2008年8月29日 星期五

Microsoft Urlscan Filter v3.0

微軟又推出了Microsoft Urlscan Filter v3.0,可用來防止 SQL Injection attack

http://www.microsoft.com/downloads/details.aspx?FamilyID=ee41818f-3363-4e24-9940-321603531989&displaylang=en (x86 版本)

http://www.microsoft.com/downloads/details.aspx?familyid=361E5598-C1BD-46B8-B3E7-3980E8BDF0DE&displaylang=en (x64 版本)

當然,已經不支援 Windows 2000 了。請 Windows 2000 的維護人員自行撰寫程式。

之前我是使用 HttpModule 來加強 asp.net 對SQL Injection 的攻擊。

如今,使用微軟的方法,也可以使用在 asp了。

2008年8月27日 星期三

Refactoring 的投影片

關於 Refactoring(重構),我一直並不了解其意義。
後來,讀了這本書後才發現,原來我也大致地遵循著相同的原則不斷的改善我的程式碼。

我將書中的第一二章節,做成了投影片,並且也改寫成 c# 3.0的版本。看來 c# 3.0 真的比 Java 來的好,程式碼又簡潔了不少。

 

投影片可於這裡下載

微軟與中華電信的 live pages

微軟與中華電信最近推出了 live pages 。很好用。

搜尋公司的名稱,可以找到哦!可是電話有些怪怪的,不是我熟悉的 87121298。

問了一下,原來是晶片卡部門在使用。

與 google maps 比較一下,見maps ,地圖的服務,無論是放大縮小的流暢度,或者是地圖的解析度,看來目前還是 Google 做的比較好。

而國內做的最久的 urmap ,可以說被雙面夾攻,輸入「金財通」是找不到東西的。看來龍頭地位不保。

看來網路地圖的應用,潛力十足,創意無限。是未來的web 服務的主流之一。

clip_image002

2008年8月26日 星期二

Asp.net 網站的模組化開發

一直以來,我們都希望開發網站時,能以模組的型式來開發。部署時,只要部署所需要的模組即可。
列如說,asp.net 應用程式Root 能容納多個模組,分別是公告、訂單、發票三個模組。
開發時,四個專案分別開發(包含asp.net應用程式Root及三個模組)
部署時,可以只部署所需要的模組,如 root + 公告,其餘的不要。

其實,方法已經有了。可見 Creating sub-projects in IIS with Web Application Projects
這樣的方式好處是簡單,而且可以分開部署。
可是,如果要談到資料交換(root 與 模組,模組與模組) 的資料交換,就又顯得複雜了。
只能靠 Session 或 database 來交換資料,並不是很好的做法。

目前我知道最好的做法,是 Web Client Software Factory, WCSF。

它不但可以模組與模組之間交換,甚至可以有依賴關係,例如公告模組依賴於權限模組。
並且又加上了 page flow (可取代之前的 User Interface Process (UIP) Application Block),還設計了權限、ObjectContainerDataSource、realtime search
連 source code 都給你了,真是物超所值。

c# 與 vb.net 互轉的網站

http://labs.developerfusion.co.uk/convert/csharp-to-vb.aspx ,看該來不錯。
一個簡單的例子,

using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Web.Configuration;
using BankPro.EI2.BusinessComponents;

namespace EI2.Utility
{
  public class CommonUtil
  {
    /// 
    /// 虛擬目錄名稱
    /// 
    public static string EInvoiceVDName
    {
      get
      {
        return WebConfigurationManager.AppSettings["VDName"];
      }
    }

    /// 
    /// 登入者所屬公司的 CompanyOid
    /// 
    public static int LoginUserCompanyOid
    {
      get
      {
        return UserBC.GetUserCompanyOidByAccount(
          HttpContext.Current.User.Identity.Name).Value;
      }
    }
  }
}

經過轉換後,可以轉成 VB.NET 無慮。
Imports System 
Imports System.Data 
Imports System.Configuration 
Imports System.Linq 
Imports System.Web 
Imports System.Web.Security 
Imports System.Web.UI 
Imports System.Web.UI.HtmlControls 
Imports System.Web.UI.WebControls 
Imports System.Web.UI.WebControls.WebParts 
Imports System.Xml.Linq 
Imports System.Web.Configuration 
Imports BankPro.EI2.BusinessComponents 

Namespace EI2.Utility 
    Public Class CommonUtil 
        '''  
        ''' 虛擬目錄名稱 
        '''  
        Public Shared ReadOnly Property EInvoiceVDName() As String 
            Get 
                Return WebConfigurationManager.AppSettings("VDName") 
            End Get 
        End Property 
        
        '''  
        ''' 登入者所屬公司的 CompanyOid 
        '''  
        Public Shared ReadOnly Property LoginUserCompanyOid() As Integer 
            Get 
                Return UserBC.GetUserCompanyOidByAccount(HttpContext.Current.User.Identity.Name).Value 
            End Get 
        End Property 
    End Class 
End Namespace 
但是,碰到新的 c# 3.0 (linq 的語法就會失敗了) 例如
protected void gvInvoices_DataBound(object sender, EventArgs e)
    {
      foreach (GridViewRow row in gvInvoices.Rows)
      {
        for (int i = 0; i < gvInvoices.Columns.Count; i++)
        {
          DataControlField item = gvInvoices.Columns[i];
          BoundField boundField = item as BoundField;
          if ((boundField != null) && string.IsNullOrEmpty(boundField.DataField))
          {
            string headerText = item.HeaderText;
            int oid = Convert.ToInt32(gvInvoices.DataKeys[row.RowIndex].Value);
            var tmpType = tmpTypes.SingleOrDefault(t => t.OID == oid && t.GroupName == headerText);
            if ((tmpType != null) && (!string.IsNullOrEmpty(tmpType.ObjectValue)))
            {
              row.Cells[i].Text = tmpType.ObjectValue;
            }
          }
        }
      }

    }
會產生 Conversion was attempted, however the following errors were reported: -- line 1 col 11: invalid TypeDecl

2008年8月25日 星期一

Oracle 學習(5) rownum 的技巧

Oracle 的 rownum 是一個 pseudocolumn,意思是不存在的 column,是經由計算得來的.

先建立範例資料 .

create table emp(id int, sid char(10) not null, name char(10) not null);

insert into emp(id, sid, name) values (1, 'Charles', 'F111222333');
insert into emp(id, sid, name) values (2, 'Jerry', 'A121121121');
insert into emp(id, sid, name) values (3, 'Abel', 'H114567898');
執行下面的語法,可得到所有資料,且得到 rownum 代表的「列號」
select id, sid, name, rownum from emp;
結果如下,很正常。
image
但如果需要依 name 來排序,答案就怪怪的。
select id, sid, name, rownum from emp order by name;

結果如下,RowNum 是先經過編號後,才再 order by name 的。 
image

所以,如果要先 order by name 才編號,就必須下這樣的sql script

select t.*, rownum from (
	select id, sid, name from emp order by name ) t

結果如下

image 
相同的問題,如果在 SQL Server 2005 上,就設計地好多了,因為語法上很清楚地說明了「編號是針對order by name」來編號。

select *, ROW_NUMBER() over (order by name) from emp;
結果如下圖。看來 sql server 的設計是較貼近 developer 的.
image

2008年8月22日 星期五

Oracle 學習(4) SQL Paging

在 SQL Paging 的功能上,一直「感覺」Oracle 會比較強。主要是之前 SQL Server 2000 與 Oracle 7 的印象。
但在 SQL Server 2005 之後呢?就沒有比較過了。
舉例來說,在 SQL Server 2005 ,可以使用下面的語法
select * from 
( 
   select *, ROW_NUMBER() over(order by invoicenumber) as rn from B2CInvoice 
) a 
where a.rn between 21 and 30 
SQL Server 2000的語法就不說了,是很差的,而且並沒有通用的版本。
那 Oracle 呢?我現在只找到如下的方法,看起來比sql server 2005 的麻煩
SELECT * FROM
     (
     SELECT A.*, rownum r
     FROM
          (
          SELECT * FROM B2CINVOICE
          ORDER BY invoiceNumber
          ) A
     WHERE rownum <= 30
     ) B
WHERE r >= 21;
注意到,下面的是沒有資料回傳的,也就是錯誤的查詢結果。雖然看起來比較直覺而且易懂
     SELECT A.*, rownum r
     FROM
          (
          SELECT * FROM B2CINVOICE
          ORDER BY invoiceNumber
          ) A
     WHERE rownum >= 21 and rownum <=30

2008年8月21日 星期四

Oracle 學習(3) boolean 的資料欄位

忍不住要發點牢騷。在 SQL Server 上,一直嘗試著Follow SQL 92 的標準。裡面有這麼一段
SQL defines distinct data types named by the following key words: CHARACTER, CHARACTER VARYING, BIT, BIT VARYING, NUMERIC, DECIMAL, INTEGER, SMALLINT, FLOAT, REAL, DOUBLE PRECISION, DATE, TIME, TIMESTAMP, and INTERVAL.

因此,bit 的data type 是 SQL 92 的標準。根據此標準,我們可以宣告一個欄位為 bit,此欄位只能存0 或 1 (因為是 bit)。
在 AP 的設計時,就會將該資料視為 boolean 。
範例如下:建立一個測試資料庫及資料表
create database test;
go
use test;
go
create table Emp(id int not null, IsRetired bit not null);
所以,有一個員工編號1號,尚未退休時,就會用下面的sql script 來 insert
insert into dbo.Emp(id, IsRetired) values (1, 0);
接下來,使用 LINQ to Sql 並寫如下的程式,找出所有未退休的員工資料
TestDataContext ctx = new TestDataContext();
var q = from e in ctx.Emps
        where e.IsRetired == false
        select e;

其中, linq to sql 自然就將 IsRetired 欄位當成 boolean 值,0 是 false, 1 是 true. 這在 sql server 是相當自然而沒有疑慮的事情。
那在 Oracle 呢?Oracle 是資料庫的老大,當然不需要 follow 標準。(所以別怪 MS Office 不照 OpenDocument format (ODF) 的標準,而要自行搞一套 Office Open Xml 的標準)
Oracle 不支援 bit 的 datatype,甚至在 Oracle vs. SQL Server中也指明了,bit 的相等的oracle 表達法是 number(1, 0)。
是否我們需要存 boolean 值時,在oracle 就宣告成 number(1, 0) 呢?
將來如果出了一個 linq to oracle 的 privider,是否看到 number(1, 0) 就自行轉成 boolean 呢? 看了很多前人的程式,都是自行定義的,如 char(1),然後寫入值為 '0' 或 '1' ,也有寫成 'Y' 或 'N",當然也有 'y' 或 'n'。
實在令人頭痛。
如果是您,不知道您會如何宣告呢?

2008年8月20日 星期三

Oracle 學習(2) Role 的權限

Oracle資料庫是相當大的層級,而與SQL Server 的層級不太相同。
例如,SQL Server 的層級是 Instance-Database-Schema-Securable

像是 server.myDB.mySchema.myTable

系統層級的權限,會設定在 instance 層級上,而資料庫層級的權限,則會在 database 層級上。因此,會有sysadmin (instance level role, 即server role) 及 db_owner (database level role) 會如此的類似。見之前的文章 db_owner 與 database owner

image image image

而Oracle 的設計是不太一樣的。Oracle 的資料庫相當的「高級」,幾乎等同於 SQL Server 的 Instance,因此system level 的 privileges 直接設在 database level。
雖然 Oracle 也可以安裝多個 instance 在同一個 server,但幾乎沒人這麼做,因為一個 instance 就會把伺服器的資源吃完了。

Oracle 資料庫內建的 role 有 connect, resource等,其實已經是相當大的權限了。例如 conect 就有 create table 的權限。
image
不知道 SQL Server database levle 的 role : db_datareader,在Oracle 上要如何設定呢?
從 Google 搜尋後,得到了如下的 answer

grant select any table to myUser
但是,這仍然只是近似的方法,並非等同的sql server 的 db_datareader 的權限。

2008年8月19日 星期二

SingleTagSectionHandler

 

使用 configuration 時,常常為了如何配置檔案而傷腦筋。

如果全部使用 appSettings,則設定將會很雜亂而難以維護。
如果使用自訂的 ConfigurationElement, 又會產生程式碼需要維護的問題。

在.net 2.0 有個很好用的 SingleTagSectionHandler 可以使用。
配置檔如下。

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="MySection" type="System.Configuration.SingleTagSectionHandler"/>
  </configSections>
  <MySection key1="A" key2="B" key3="C"/>
</configuration>

讀設定時,可以用下面的方法讀出資料

      Hashtable ht = ConfigurationManager.GetSection("MySection") as Hashtable;
      string key1 = ht["key1"].ToString();
      string key2 = ht["key2"].ToString();
      string key3 = ht["key3"].ToString();
code download

2008年8月18日 星期一

SQL Server 2008 的備份有壓縮功能

使用 SQL Server 2008 備份時,有內建的壓縮功能哦!

image

這樣一來,使用的磁碟或磁帶空間就變小了,restore 時也會加快。
雖然比較偒CPU,但在緊急回復時,時間比較重要吧。

測試的結果,原本需要 1.48GB 的空間,壓縮後只需要 277MB

2008年8月15日 星期五

What Is Refactoring?

What Is Refactoring?
Refactoring is the process of changing a software system in such a way that it does not alter the
external behavior of the code yet improves its internal structure. It is a disciplined way to clean up
code that minimizes the chances of introducing bugs. In essence when you refactor you are
improving the design of the code after it has been written.

 

from Refactoring: Improving the Design of Existing Code

2008年8月13日 星期三

微軟 Visual Studio.NET 2003 的主流支援到 2008/10/14

提醒一下。由於微軟的 mainstream support 策略,使用Visual Studio.NET 2003 的主流支援到 2008/10/14,見這裡

也就是說,除非你自己出錢請微軟修改,否則 Visual Studio.NET 2003 不會再新增功能了。

建議開發新專案時,請使用 Visual Studio 2008,至少主流支援可到 2013 年。

Visual Studio 2005 也可以到 2011

2008年8月12日 星期二

Sql Server Management Studio 2008 的 intellisense 不見了?

原本使用 CTP, RCO 時,Sql Server Management Studio 2008 的 intellisense 非常的好用。但我發現,正式版的 SSMS 不能連到2000, 2005 上了!! 這是真的。

When IntelliSense Is Unavailable. 上有一這麼一段 Intellisense is only available when the Database Engine Query Editor is connected to an instance of the SQL Server 2008 Database Engine.

MS 也真的好樣的!

Visual Studio 2008 sp1 and .NET Framework 3.5 sp1 final release

終於出來了。見 http://blogs.msdn.com/charlie/archive/2008/08/11/released-visual-studio-service-pack-1-net-3-5-service-pack-1.aspx 

因為,安裝 SQL Server 2008的client tool 時失敗了。原因是我的開發電腦上安裝的 Visual Studio 2008 尚未升級成 sp1。不讓我安裝。
當時就知道沒差幾天 Visual Studio 2008 就會出 sp1,否則會影響多少 developer 啊。

2008年8月8日 星期五

原來,DataGridView(WinForm) 與 GridView(Asp.net) 是如此的不同

一直都在實作 asp.net,很少寫 window form 的程式。
現在才發現,這兩個是如此的不同。

在 Asp.Net 的 GridView,簡單的程式如下是可以運作的

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="GridViewDataBid_AspNet._Default" %>


  
code behind
protected void Page_Load(object sender, EventArgs e)
    {
      string[] ary = { "A", "B", "C" };
      GridView1.DataSource = ary;
      GridView1.DataBind();
    }

執行結果如下圖
image
寫起來實在容易。而在 Windows Form這麼做的話,得到的結果是一片空白

private void Form1_Load(object sender, EventArgs e)
    {
      string[] ary = { "A", "B", "C" };
      dataGridView1.DataSource = from s in ary
                                  select new
                                  {
                                    Name = s
                                  };
    }
記得在Form 上的 DataGridView ,要新增一個 Name 的 column

這一段不成功的原因是,DataGridView.DataSource 必須是 IList 介面,包括一維陣列。 IListSource 介面,例如 DataTable 和 DataSet 類別。 IBindingList 介面,例如 BindingList 類別。 IBindingListView 介面,例如 BindingSource 類別。 ( see here )

上一段的程式,只會造成 IEnumable<T>。因此,GridView.DataSource 可以吃,而DataGridView.DataSource 不行。 改一下程式,就可以看到結果了
private void Form1_Load(object sender, EventArgs e)
    {
      string[] ary = { "A", "B", "C" };
      dataGridView1.DataSource = (from s in ary
                                  select new
                                  {
                                    Name = s
                                  }).ToList();
    }
code sample download

2008年8月6日 星期三

DateTime.Parse("中華民國97年2月29日")?

同事問到,如何將 "中華民國97年2月29日" 的字串轉成 DateTime, 與將 DateTime 輸出成 "中華民國97年2月29日"

沒遇過這個問題。

打開 MSDN, 找到了 TaiwanCalendar

開始寫程式囉

      DateTime dt = new DateTime(2008, 2, 29);
      TaiwanCalendar c = new TaiwanCalendar();
      CultureInfo ci = new CultureInfo("zh-TW", true);
      ci.DateTimeFormat.Calendar = c;
      Thread.CurrentThread.CurrentCulture = ci;
      Console.WriteLine(dt.ToLongDateString());

      DateTime dt2;

      dt2 = DateTime.Parse("97-02-29", ci);
      Console.WriteLine(dt2.ToLongDateString());

      dt2 = DateTime.Parse("97/02/29", ci);
      Console.WriteLine(dt2.ToLongDateString());

      dt2 = DateTime.Parse("97-2-29", ci);
      Console.WriteLine(dt2.ToLongDateString());

      dt2 = DateTime.ParseExact("97|02|29", "yy|MM|dd", ci);
      Console.WriteLine(dt2.ToLongDateString());

      dt2 = DateTime.ParseExact("中華民國97年2月29日", "中華民國yy年M月dd日", ci);
      Console.WriteLine(dt2.ToLongDateString());

      dt2 = DateTime.ParseExact("中華民國97年2月29日", "中華民國yy年M月dd日", ci);
      Console.WriteLine(dt2.ToLongDateString());

      dt2 = DateTime.Parse("97/02/29 下午 3:46:01");
      Console.WriteLine(dt2.ToString());

輸出的結果如下
97年2月29日
97年2月29日
97年2月29日
97年2月29日
97年2月29日
97年2月29日

sample code download here

Oracle 學習(1)-- Create User

目前需要用到 Oracle。不過,之前使用 Oracle 已經是十年前的事了(Oracle 7)。 現在的變化應該很大了。 將Study 的過程記錄下來,應該對之後有幫助。
--Create User
set timing off;

spool log\createMyDbReader.log;

create user MyDbReader identified by MyPassword
default tablespace users
temporary tablespace temp
quota unlimited on users
quota unlimited on INDX
quota unlimited on objects
quota unlimited on temp
;
grant create session to MyDbReader;
grant unlimited tablespace to MyDbReader;
grant query rewrite to MyDbReader;

spool off;
exit;

--使用 MyDbReader 登入後更改 password 為 MyNewPassword
connect MyDbReader/MyPassword@MyServer ;

alter user MyDbReader identified by MyNewPassword

--刪除使用者
drop user MyDbReader;

2008年8月4日 星期一

AJAX 之 PageMethod

之前使用 Asp.Net 時,一直使用 UpdatePanel。這個實在是好用。
但是,我卻誤解了,誤以為使用 Ajax 一定會降低頻寬。

我寫了一個範例 , 一個使用傳統的 UpdatePanel,一個使用 PageMethod。
可以使用 fiddler 之類的工具,查到ajax 所使用的 request 及 response 內容。

大小差別如附圖。

image

使用 UpatePanel 的版本,Request (244 bytes)及 Response(648 bytes) 的內容竟然包山包海,根本無法將傳輸的資料量降低。
image
竟然還有 __VIEWSTATE!? 

原來,asp.net ajax team 當初設計 UpdatePanel 的目的,在於簡化開發的過程。因此對於傳回的 request 內容 ,幾乎等同於未 ajax 的版本。相同地, Response 也是幾乎等同於未 ajax 的版本。只不過不必自己塞到不同的 html control 內了。

 

相反地,使用 PageMethod 是一個相當經清的方法。

  1. 將 ScriptManager 的 EnablePageMethods 屬性設寫 true
  2. 在 code behind 程式加上這一段
        [WebMethod]
        [ScriptMethod]
        public static string GetCurrentTime()
        {
          return DateTime.Now.ToString();
        }
  3. 最後,要寫 javascript,讓 button click 時呼叫

function Button2_onclick() {
  PageMethods.GetCurrentTime(ShowTime);
}

function ShowTime(value)
{
  $get("Label2").innerHTML = value;
}
這樣的好處是,傳輸的資料可直接控制。Requet 0 bytes,Response 27 bytes。
Response 的內容為 {"d":"8/4/2008 3:20:09 PM"}。夠精簡吧。 不過這樣一來,資料的內容要塞到 html control 的功夫可不小啊。範例中的simple case,簡直不可能發生。
想想當要顯示一個客戶資料到一個 DetailView 要花多少的javascript程式碼啊!!!

Share with Facebook