星期一, 12月 17, 2012

Console 執行 LINQPad 檔案

話說科技始終來至於人性,懶惰來自於內心,基於這兩點,Bibby 我很常很常用 LINQPad 來幫我做一些有的沒有的測試,因為 LINQPad 很方便,可以不用開任何新 Project 就可以開始寫 Code 了,它的方便和實用性,不用我再多加累述,不清楚的可以看我好朋友 Kevin 這篇文章 LINQPad - 好用到爆炸、.NET開發人員必備的好用工具,就可以明瞭上手。今天為大家介紹一下,稍微另類的用法,簡單說就是 Bibby 我就是想要懶,當我們 LINQPad 的 Code 寫好後,如果需要排程使用,還是要開新的 Project 來把 寫好的 Code 移過去,基於懶惰不再重工的原則(這啥鬼原則),所以在想可不可以直接用 Command 的方式,呼叫就給它用下去,本來想寫個 Console 來解析 LINQPad 的 Code 的,搜尋一下發現,已經有人佛心幫我們寫好了這個小工具,那就是 LINQPadRunner,這個工具可以在 Command 下直接使用,例如

C:\LPRun.exe MyTestLINQPad.linq

也可以多幾個參數

C:\LPRun.exe MyTestLINQPad.linq “aa” “bb” “cc”

這兩種的執行方法,完全達到我想"懶"的原則,讚!打開專案看看 Source Code 是怎麼寫的,可以看到作者是用 CSharpCodeProvider 的類別來動態編譯 LINQPad 裡面的 Code,專案裡面的 Code 沒有多少,有興趣的可以自己參考一下,還蠻好玩的!

這裡提供一下個人覺得優點和缺點,讓大家可以評估一下

優點是

  1. 不需要裝 LINQPad 就可以直接執行 LINQPad 的檔案
  2. 不需要編譯,也不需要 Visual Studio,直接用文字編輯器就打開 .linq 的檔案直接修改
  3. 檔案很單純,一個 LPRun.exe 執行檔還有一個寫好的 .linq 就可以跑了,不像開個專案一樣,一堆有的沒有的東西
  4. 可以 Reference 其他的 dll 進來,用法都跟 LINQPad 內的方式一樣

缺點是

  1. 需要有裝 .NET Framework 才可以跑(沒有這東西,好像做啥都是多說的)
  2. 不能用 Extention Method (其實改 LINQPadRunner Source Code 後應該就行)
  3. 執行會慢一點點點,因為是動態編譯執行的關係

以上大概就是這樣,有問題一起討論吧!!!

Referenece
https://github.com/mcintyre321/LINQPadRunner
http://msdn.microsoft.com/zh-tw/library/microsoft.csharp.csharpcodeprovider%28v=vs.80%29.aspx

星期五, 11月 16, 2012

Convert IEnumable to IEnumable<T>

今天來這分享一下使用 Linq 遇到的小問題,當我們 Linq 用越用熟後,遇到集合 (Collecion) 都會習慣用 Linq 去解決它,今天在 WebFrom 的 Project 裡面,想要把某個 Control 下面所有個 TextBox 的 Type 抓出來,下了這一段程式

PlaceHolder1.Controls.Where(a=>a is TextBox)

結果就給我暴了..><..

為什麼會暴呢?查了 Controls 的 Source Code 看仔細,原來是 PlaceHolder1.Controls 只有實作 IEnumerable..

public class ControlCollection : ICollection, Ienumerable

Linq 常用 Extention Method 大部分都要實作 IEnumerable<T> 這個泛型,如 Where, OrderBy 等,所以沒有腦袋直直想的解決的方法就是把 IEnumable 轉成 IEnumable<T>..XD..

一開始想自己寫一個 Extention Method,不過太陽底下沒有新鮮事,MSND 找了一下,查到可以轉換的有兩個 Method,就是 OfType<T> 和 Cast<T> 這兩個 Extention Method,貼一下 MSDN 的解釋
http://msdn.microsoft.com/zh-tw/library/bb360913.aspx
Enumerable.OfType<TResult>
方法根據指定的型別來篩選 IEnumerable 的項目。

http://msdn.microsoft.com/zh-tw/library/bb341406.aspx
Enumerable.Cast<TResult>
方法將 IEnumerable 的項目轉換成指定的型別。

簡單說就是轉型成 IEnumable<T> 啦,使用時候要小心,如果用 Cast<T> 來做,集合裡面有其他 Type 的 Contnrol 會暴掉,所以我是用 OfType<TResult> 來處理

最後解決方法就是這樣啦..

PlaceHolder1.Controls.OfType<TextBox>()

有問題再討論吧!!!

星期四, 11月 08, 2012

SQL Data Project with SQL Azure

最近把自己的一個專案放到了 Windows Azure 上面,整個佈署的感覺還蠻不錯的,所以在這裡跟大家分享一下佈署的經驗。

網路上已經有好心的大大教我們怎麼把網站佈署到 Windows Azure 了

使用Windows Azure WebSite幫客戶迅速搭建網站(董老師)
 
Windows Azure - 使用FTP上傳檔案到Web Sites並取得MySQL帳號與密碼(好朋友 SKY)
 
微軟Azure測試心得分享(二) :新版管理介面+快速啟用Web Sites
 
還有資料庫要如何佈署上 SQL Azure

利用 SQL Database Migration Wizard 移轉 SQL Server 2005-2012 到 Windows Azure SQL Database
 
SQL Azure - 使用SQL Azure Migration Wizard將本地的資料庫搬移到SQL Azure(好朋友 SKY)
 
如何佈署網站和資料庫到 Windows Azure,照上面那幾篇文章的方式,應該沒有啥太大的問題,這次的文章,主要著重在 SQL Azure 上,因為在佈署的時候,Database 這部份因該是比較容易困擾大家的,不是說資料上不去的問題,而是之後如果 DB Schema 變動,或是想用 design 的介面來改變 SQL Azure,那就會有點痛苦了,為什麼會痛苦呢?我們直接看圖說故事吧!

SQL Azure 點選 table 的右鍵

clip_image001

原 SSMS 點選 table 的右鍵

clip_image002

在 table 上按右鍵,很明顯可以看出來,很多東西被閹割了XD..被 SSMS 寵壞的我,要用 T-SQL 來直接新增 table、新增 index 根本是一整個痛苦,所以上了 SQL Azure 就不會想再去動 DB Schema,但是這情況怎麼可能會發生,所以懶惰習慣的我,當然想一些有的沒有的方式來把這困擾解決掉,科技始終來至於人性,當工程師不懶惰做啥鬼工程師..^^||..

Bibby 的方式就是直接用 SQL Server Database Project 來幫我處理這件事,如果對 SQL Database Project 不熟的,可以去看一下 Bibby 之前的文章"SQL Server Database Project"複習一下,接下來就看圖說故事摟!

先看流程

clip_image003
  1. 當有新需求的時候,一定會先改 Test Database Server 上的 Schema,當改好測試完後,就可以準備上 Code 了
  2. 藉由比對的方式,把 Test Database Server 上修改的 DB Schema 的更新到 SQL Server Database Project,通常如果專案有版本控管的工具,我們也會把修改變更的內容更新到我們的版控上
  3. 藉由比對的方式,把 SQL Server Database Project 變更的檔案更新到 SQL Azure 上,這樣就大功完成了,包括新增 table,新增 index 都可以用這方法簡單的做好
上述的流程,都在"SQL Server Database Project"文章中找到,反正如果需求要改 DB Schema ,那就照流程 1,2,3 按圖施工給他做一變就是了!但是..事情不是像愚人想的這麼簡單,還是會有些小石頭來絆你的,Bibby 也把跌來跌去的經驗分享給大家,讓大家參考參考!

問題一

當你看到下面這錯誤

clip_image004

記得去設定裡面把 "Allow incompatible platform" 打勾,這樣才能產出"合適"的 SQL Statement

clip_image005






clip_image006

問題二
 
clip_image007

解決方法去把"Block on possible data loss"這選項打勾吧!

clip_image008

理由跟原因,看下方網址吧!

http://social.msdn.microsoft.com/Forums/en-US/vstsdb/thread/7799b2d2-a067-47d5-9cf3-d0332baa2ebf

問題三
 
clip_image009

這跟你說,你有些 table 沒有加 cluster index 請去加上去,SQL Azure 才允許你上傳!

問題四
 
clip_image010

這問題是權限的問題啦,會讓你編譯不過,你只要把 Security 的資料夾 Exclude From Project

clip_image011

大概就這樣啦!,希望大家都可以順利上 Azure ,有問題再一起討論吧!!

星期四, 11月 01, 2012

console.log

近來的專案越來對使用者體驗( user friendly )更加的重視,對於 client 端的程式需求越來越多,而在 Web Application 的專案裡面,client端的程式的首選當然是 javascript ,今天來分享一下對於 javascript 開發的小技巧。

在比較古老的年代,常常會倚賴 alert 這 method 來協助程式的撰寫,或是在 debug 的時候也拿來用,但是用 alert 來做這方面的處理,真的很不適合,例如:1.跳出來訊息要手動去關掉 2.正式上線時,不能留著 3.在大量訊息時,alert視窗就會爆炸等等等,這些缺點都導致開發起來礙手礙腳的,在強調快速開發且又需要大量撰寫的 javascript 的時代,現在都會用 console.log 來代替 alert,但是 console.log 又有一些缺點,那就是不是所有瀏覽器都支援 (我沒有指名道姓說IEx我沒有指名道姓說IEx),參考 黑暗大的 jQuery Logging Plugin 這篇文章,再加上自己本身的一些經驗,整合了一下作法,我們先直些看code吧!

實作

//擴充 String ,讓 String 可以有 log 的能力
jQuery.extend(String.prototype, {
    log: function (logKey) {
        var msg = this.toString()
        jQuery.fn.log.logConsole(logKey, msg);
        return msg;
    }
});
//參考 黑暗大的 jQuery Logging Plugin
jQuery.fn.log = function (logKey) {
    jQuery.fn.log.logConsole(logKey, this);
    return this;
};
jQuery.fn.log.logConsole = function (logKey, obj) {
    if (typeof console != "undefined")
        console.log("%s: %o", logKey, obj);

}

用法

var str = 'I love twMVC',
      elem = $('<div>test1</div>');
      ;    

var a = str.substr(7,5).log('s1').substr(2,3).log('s2');
elem.log('jqueryElem').hide();

image
這裡我加了對 String 的擴充,很多時候都需要把 String 顯示出來,讓自己可以有更好的參考來撰寫 javascript ,當要上正式機的時候,只需要把擴充裡的 console.log 那段註解掉就行了,對於其它有加 log 的地方都不需要改動,這樣也不會手賤手誤改錯了地方了,個人感覺算不錯的方式,跟大家分享一下,如果有更好的方法,交流一下吧

參考:


星期二, 10月 02, 2012

Picasa with C#

最近有個專案,有需要把圖片分享給他人,沒有很多的 resource,所以需要找一個免費又可以依賴的 service,噹噹噹..所以找到 google 的 Picasa 這個東東,在這裡做個筆記,讓大家以後有需要用到這服務的朋友,可以不用再花很多時間去尋找..

先把 picasa 限制說出來,有需要的評估一下:

1.相片大小上限:圖片一律不能超過 20 MB 及 5000 萬像素。
2.影片大小上限:上載的單一影片大小一律不能超過 1GB。
3.網路相簿數量上限:10,000本
4.每本網路相簿的相片和影片數量上限:1,000張
5.總儲存空間:「Picasa 網路相簿」可儲存 1 GB 的相片和影片。
6.免費儲存空間限制:只要相片大小低於 800x 800 像素,影片長度少於 15 分鐘,就不會計入免費儲存空間配額。
7.如果加入 Google+ 將獲得更多免費儲存空間:系統會將您上載至 Google+ 的相片自動調整成 2048 像素大小,並免費儲存這些相片。如同「Picasa 網路相簿」,您可以免費上載的長度 15 分鐘以內的影片。

http://support.google.com/picasa/bin/answer.py?hl=zh-Hant&answer=43879
http://support.google.com/picasa/bin/answer.py?hl=zh-Hant&answer=1321133
http://support.google.com/picasa/bin/answer.py?hl=en&answer=1224181

優點是:

1.如果照片在 800 * 800 像素以下,我們有 10000000(10,000x1,000) 張照片可以使用,這樣對一般使用者來說幾乎就沒有空間限制
2.可以照片直接外連
3.Picasa 裡面的 folder 限制瀏覽但是檔案可以外連
4.google 牌子夠大,不怕倒(不過基本備份還是自己要有)

缺點是:

1.流量限制,我在官方的 api 裡面,並沒有找到很明確的說明,不過以網路上的一些說法是,流量太大就把你擋起來(這這這..來陰的)

看了上列的優點和缺點以後,要不要使用這個服務,心理就有個底了,不過 Bibby 我是覺得 Google Picasa 是可以用用看,所以就參考了一下 Google API,搜尋一下 C# 要如何使用這個免費的服務,準備用 C# 來把它寫起來..

開 nuget ,直接載下列這三個 reference,這樣就可以使用了..

Google.GData.Photos
Google.Picasa
Google.GData.Client

不過如果您載下來就會用,Bibby 跟您拜一下了,因為真的不知道如何下手XD..所以又在網路上找了一堆有的沒有的資料,終於裡出了一點頭緒,整理一下常用的功能如:"新增相簿" "刪除相簿" "上傳照片" "刪除照片" "下載照片" 等這些功能,給大家參考參考(吃齋念佛做好事說好話佛心來著低^^)

public class GooglePicasaUtilities
{
PicasaService myPicasa;
string serviceName = "Test-1.0";

string _TargetAccount;
public string TargetAccount
{
get
{
if (string.IsNullOrEmpty(_TargetAccount))
throw new Exception("Please enter the target account.");
return _TargetAccount;
}
set
{
_TargetAccount = value;
}
}

public GooglePicasaUtilities(string username, string password)
{
myPicasa = new PicasaService(serviceName);
myPicasa.setUserCredentials(username, password);
}

public GooglePicasaUtilities(string username, string password,
string targetAccount)
: this(username, password)
{
TargetAccount = targetAccount;
}

//download photo
public void DownloadPhoto(string albumName, string photoTitle,
string saveFolder, ImageFormat imageFormat)
{
PicasaFeed myPicasaFeed = GetPicasaFeed(albumName);
var item = myPicasaFeed.Entries
.Where(a => a.Title.Text == photoTitle)
.FirstOrDefault();
if (item != null)
{
Stream mystream = myPicasa.Query(new Uri(item.Content.Src.ToString()));
Bitmap m = new Bitmap(mystream);
string path = string.Format("{0}.{1}",
Path.Combine(saveFolder, item.Title.Text),
imageFormat.ToString().ToLower());
m.Save(path, imageFormat);
}
}

//delete photo
public void DeletePhoto(string albumName, string photoTitle)
{
PicasaFeed myPicasaFeed = GetPicasaFeed(albumName);
var item = myPicasaFeed.Entries
.Where(a => a.Title.Text == photoTitle)
.FirstOrDefault();
if (item != null)
{
item.Delete();
}
}

//delete album
public void DeleteAlbum(string albumName)
{
AlbumQuery myAlbumQuery =
new AlbumQuery(PicasaQuery.CreatePicasaUri(TargetAccount));
PicasaFeed myPicasaFeed = myPicasa.Query(myAlbumQuery);

var album = myPicasaFeed.Entries
.FirstOrDefault(a => a.Title.Text == albumName);
if (album != null)
album.Delete();
}

//get photo info list
public List<PicasaPhotoInfo> GetPhotoInfoList(string albumName)
{
PicasaFeed myPicasaFeed = GetPicasaFeed(albumName);
//List<Google.GData.Photos.PicasaEntry> data=new List<Google.GData.Photos.PicasaEntry>();
var data = new List<PicasaPhotoInfo>();
foreach (PicasaEntry p in myPicasaFeed.Entries)
{
data.Add(new PicasaPhotoInfo()
{
Id = p.Exif.ImageUniqueID.Value,
Title = p.Title.Text,
Summary = p.Summary.Text,
Url = GetRealUrl(p.Media.Content.Url)
});
}
return data;
}

//Upload a image.
public void UploadImage(string albumName, byte[] imageByteArr,
string photoTitle)
{
string albumId = GetAlbumId(albumName);
Uri newURI = new Uri(PicasaQuery.CreatePicasaUri(TargetAccount, albumId));
using (Stream imageStream = new MemoryStream(imageByteArr))
{
myPicasa.Insert(newURI, imageStream, "image/jpeg", photoTitle);
}
}

//create new album
public void InsertNewAlbum(string title, string summary)
{
AlbumQuery myAlbumQuery =
new AlbumQuery(PicasaQuery.CreatePicasaUri(TargetAccount));
PicasaFeed myPicasaFeed = myPicasa.Query(myAlbumQuery);

Album newAlbum = new Album();
newAlbum.Title = title;
newAlbum.Summary = summary;
myPicasa.Insert(myPicasaFeed, newAlbum.PicasaEntry);
}

//Get album info list
public List<PicasaAblumInfo> GetAlbumInfoList()
{
AlbumQuery myAlbumQuery =
new AlbumQuery(PicasaQuery.CreatePicasaUri(TargetAccount));
PicasaFeed myPicasaFeed = myPicasa.Query(myAlbumQuery);

var rtn = new List<PicasaAblumInfo>();
foreach (PicasaEntry p in myPicasaFeed.Entries)
{
Album myAlbum = new Album();
myAlbum.AtomEntry = p;

rtn.Add(new PicasaAblumInfo()
{
Id = myAlbum.Id,
Title = myAlbum.Title,
Summary = myAlbum.Summary
});
}
return rtn;
}

Dictionary<string, string> albumIdDicTemp = new Dictionary<string, string>();
private string GetAlbumId(string albumName)
{
if (albumIdDicTemp.ContainsKey(albumName))
return albumIdDicTemp[albumName];

var infoList = GetAlbumInfoList();
albumIdDicTemp= infoList.ToDictionary(a=>a.Title,b=>b.Id);
if(albumIdDicTemp.ContainsKey(albumName))
return albumIdDicTemp[albumName];
return "";
}

Dictionary<string, PicasaFeed> PicasaFeedDicTemp =
new Dictionary<string, PicasaFeed>();
private PicasaFeed GetPicasaFeed(string albumName)
{
if (PicasaFeedDicTemp.ContainsKey(albumName))
return PicasaFeedDicTemp[albumName];

string albumId = GetAlbumId(albumName);
PhotoQuery myPhotoQuery =
new PhotoQuery(PicasaQuery.CreatePicasaUri(TargetAccount, albumId));
var pf = myPicasa.Query(myPhotoQuery);
PicasaFeedDicTemp[albumName] = pf;
return pf;
}

private string GetRealUrl(string url)
{
var arr = url.Replace("https://", "").Split('/').ToList();
arr.Insert(arr.Count() - 1, "s1600");
return string.Concat("https://" + string.Join("/", arr.ToArray()));
}
}

public class PicasaPhotoInfo
{
public string Id { get; set; }
public string Title { get; set; }
public string Url { get; set; }
public string Summary { get; set; }
}

public class PicasaAblumInfo
{
public string Id { get; set; }
public string Title { get; set; }
public string Summary { get; set; }
}


使用方式


var picasa = new GooglePicasaUtilities(targetAcccount, targetPassword,targetAcccount);

//---about album---
//create Album
picasa.InsertNewAlbum(targetAlbumName,"summary");

//delete album
picasa.DeleteAlbum(targetAlbumName);

//get album info
picasa.GetAlbumInfoList();

//get photo list by albumName
picasa.GetPhotoInfoList(targetAlbumName);

//---about photo
//download photo
picasa.DownloadPhoto(targetAlbumName,photoTitle,saveFolder,ImageFormat.Bmp);

//delete photo
picasa.DeletePhoto(targetAlbumName,photoTitle);

//upload image
using(FileStream ms=File.OpenRead(filePath))
{
//bmap.Save(ms, ImageFormat.Jpeg);
byte[] bytes = new byte[ms.Length];
ms.Read(bytes, 0, bytes.Length);
ms.Seek(0, SeekOrigin.Begin);
picasa.UploadImage(targetAlbumName,bytes,photoTitle);
}


大概就這樣了,有需要的就拿去用吧!有 bug 記得別找我^^
有問題再一起討論吧..

參考
http://playpcesor.blogspot.com/2011/06/google-android-app-picasa.html
http://abintech.twidv.com/2009/02/blogger-picasa.html

星期五, 8月 31, 2012

SQL Server Database Project

今天為大家介紹一下 Visual Studio 2102 2012 SQL Server Database Project,不知道大家有沒有用過這個 Database 專案,這個東西相當好用,如果搭配上版本控管工具,可以把我們對於 Database 的 Schema 修改都記錄起來,這樣對於專案的控管都會有所依據,所以直接看圖說故事吧!

開啟專案

clip_image001

開完專案後,我們把我們的 Database 匯進來了

clip_image002

一開始並沒有 Database 連線可以給我們選擇,我們只要去新建就好

clip_image003

新建的方式很簡單,該填的填一填

clip_image004

這樣我們選用剛剛的 Database 連線,如果要把"權限"還有"Database 設定"都匯進來,那把下面的兩個選項都打勾

clip_image005

噹噹噹,我們就可以看到 Database 的資料都匯進來了

clip_image006

看一下匯進來的東西,這樣初始化就完成了

clip_image007

接下來介紹一下 Schema Compare 這個功能,這功能可以比較"來源"與"目標" Schema 的不同,這樣說好像沒啥感覺,舉個常會用到的例子好了,例如我在 Database Server 上改了 Schema,要如何運用這功能來達到比較與同步的效果,看圖繼續說故事吧!
開一個新的 Schema Comparison

clip_image008

設定好"來源"跟"目的",按 Compare 按鈕

clip_image009

由比較就可以看到哪個地方改掉了,如果確定要同步,按下 Update 按鈕就行了,一切都這麼自然,這樣兩邊的 Schema 就同步了,通常 Schema 同步後,Bibby 我都會上版本控管,這樣就可以去追蹤哪個版本修改了哪些欄位哪些內容

clip_image010

不過 Database Project 功能不只是這樣而已,其實我們也可以反向過來做處理,例如我們今天把方案總管的 dbo -> Table -> Test.sql 打開,我們就可以看到下面這個圖形化介面,我們可以直接做處理,例如把某個欄位刪掉,欄位改名等..

clip_image011

我們改完後,把我們之前設定的"來源"跟"目的"反向,因為比較要用 Database Project 的來當"來源",按下 Compare 後就可以比較出來,兩邊的不同,如果要同步,也可以用之前的方式,按下 Update 按鈕就可以了,不過有時候有個問題,我們不一定都可以碰到 Dasebase Server,所以改個方式按下 Generate Script 按鈕,產出 Script ,讓可以動 Database Server 的人執行

clip_image012

這就是產出來的 Script

clip_image013

不過 Bibby 在使用個時候沒有這麼順利,有遇到一個錯誤訊息"A project which specifies SQL Server 2012 as the target platform cannot be published to SQL Server 2008."這個的意思是說,我的 SQL Server 不是 2012 版本,被嫌棄了XD,要解決的方法只能很委屈的去條 vs 2012 的設定…

clip_image014
image

基本運用大概就這些,Bibby 我在開發的時候都有測試的DB,開發了一段時間後,要上正式機時,就把自己的 Database Project 恢復到上次上正式機之前的版本,然後比較一下測試的DB,這樣就可以產出這次要上正式機的 Script 來,用這方式就不怕自己開發的這一段時間 DB 部分改了多少 Schema 啦,大概就是這樣了,有問題再一起討論吧!

Reference:
http://social.msdn.microsoft.com/Forums/en-US/ssdt/thread/312ed73e-f70d-4072-ab2b-77d417dbfee5
http://blog.nhaslam.com/2012/08/20/vs2012-schema-comparison/

星期四, 8月 16, 2012

整合 NLog 到 Elmah

在專案上,log 功能一直是每個專案都要必備的東西,log 做的好,系統問題煩惱少,在 log 功能實作方面,Bibby 我是選用 thrid party 元件 NLog 來處理我需要的功能,NLog 的好用不用我多說,可以參考好友 Kevin一系列文章,介紹非常詳細了,看完了就可以成為 NLog 達人了,那今天要來說啥呢?當然是有特別的需求啦,Nlog 使用上方便,很自由也很詳細,不過問題是要如何很快速的看到 NLog 所記錄的內容,這點比較困擾我,所以問了身旁的一些好朋友,看有沒有比較不錯的方法,好朋友 Dino 給我一些建議說可以參考一下這個連結,看完了這個連結後,又不小心看到 debug 使用的 Elmah 顯示畫面,心理馬上有個譜了..那就雙璧合一水乳交融來一下吧!

我們直接實作開始,Bibby 我這邊 Elmah 是使用 SQLite 來處理的(參考這篇),所以這邊我的 NLog 也會以 SQLite 為主,不過我還是把 MSSQL 的寫好了,如果要用就把註解拿掉就行了(我是好心人 XD)..

第一步,實作 TargetWithLayout

[Target("Elmah")]
public sealed class ElmahTarget : TargetWithLayout
{
    static ElmahTarget()
    {
        ConfigurationItemFactory.Default.Targets
                .RegisterDefinition("Elmah", typeof(ElmahTarget));
    }
 
    public ElmahTarget()
    {
    }
 
    public string ConnectionStringName { get; set; }
 
    protected override void Write(LogEventInfo logEvent)
    {
        var connectionString = 
            ConfigurationManager.ConnectionStrings[ConnectionStringName].ConnectionString;
 
        using (var connection = new SQLiteConnection(connectionString))
        {
            connection.Open();
 
            var application = System.Web.Hosting.HostingEnvironment.ApplicationID;
            var time = logEvent.TimeStamp.ToUniversalTime();
            var eventData = from p in logEvent.GetType().GetProperties()
                            where p.PropertyType.IsValueType || p.PropertyType.IsEnum
                            select new XElement("item",
                                                new XAttribute("name", p.Name),
                                                new XElement("value",
                                                    new XAttribute("string",
                                                        p.GetValue(logEvent, null).ToString())));
            var variables = from k in HttpContext.Current.Request.ServerVariables.AllKeys
                            let v = HttpContext.Current.Request.ServerVariables[k]
                            select new XElement("item",
                                                new XAttribute("name", k),
                                                new XElement("value",
                                                                new XAttribute("string", v)));
            var xdoc = new XElement("error",
                                    new XAttribute("host", System.Environment.MachineName),
                                    new XAttribute("type", logEvent.Exception == null ?
                                                            "" : logEvent.Exception.ToString()),
                                    new XAttribute("message", logEvent.FormattedMessage),
                                    new XAttribute("source", logEvent.LoggerName),
                                    new XAttribute("detail", logEvent.StackTrace == null ?
                                                            "" : logEvent.StackTrace.ToString()),
                                    new XAttribute("time", time.ToString("s") + "Z"),
                                    new XElement("eventData", eventData),
                                    new XElement("serverVariables", variables));
            var user = string.Empty;
            if (HttpContext.Current != null && HttpContext.Current.User.Identity.IsAuthenticated)
            {
                user = HttpContext.Current.User.Identity.Name;
            }
 
            var SQL = @"
insert into [error]
(Application,Host,Type,Source,Message,User,AllXml,StatusCode,TimeUtc)
values
(@Application,@Host,@Type,@Source,@Message,@User,@AllXml,@StatusCode,@TimeUtc)
";
            var cmd = new SQLiteCommand(SQL, connection);
            cmd.CommandType = CommandType.Text;
            //cmd.Parameters.Add("@ErrorId", DbType.).Value = Guid.NewGuid();
            //cmd.Parameters.Add("@ErrorId", DbType.Int32).Value = errorId;
            cmd.Parameters.Add("@Application", DbType.String).Value = application;
            cmd.Parameters.Add("@Host", DbType.String).Value = System.Environment.MachineName;
            cmd.Parameters.Add("@Type", DbType.String).Value = "NLog-" + logEvent.Level.Name;
            cmd.Parameters.Add("@Source", DbType.String).Value = logEvent.LoggerName;
            cmd.Parameters.Add("@Message", DbType.String).Value = logEvent.FormattedMessage;
            cmd.Parameters.Add("@User", DbType.String).Value = user;
            cmd.Parameters.Add("@AllXml", DbType.String).Value = xdoc.ToString();
            cmd.Parameters.Add("@StatusCode", DbType.Int32).Value = 0;
            cmd.Parameters.Add("@TimeUtc", DbType.DateTime).Value = time;
 
            cmd.ExecuteNonQuery();
        }
 
    }
 
    //for ms-sql
    //protected override void Write(LogEventInfo logEvent)
    //{
    //    var connectionString = 
    //          ConfigurationManager.ConnectionStrings[ConnectionStringName].ConnectionString;
        //using (var connection = new SqlConnection(connectionString))
        //{
        //    connection.Open();
 
        //    var application = System.Web.Hosting.HostingEnvironment.ApplicationID;
 
        //    var time = logEvent.TimeStamp.ToUniversalTime();
 
 
        //    var eventData = from p in logEvent.GetType().GetProperties()
        //                    where p.PropertyType.IsValueType || p.PropertyType.IsEnum
        //                    select new XElement("item",
        //                                        new XAttribute("name", p.Name),
        //                                        new XElement("value",
        //                                                     new XAttribute("string", 
        //                                       p.GetValue(logEvent, null).ToString())));
 
        //    var variables = from k in HttpContext.Current.Request.ServerVariables.AllKeys
        //                    let v = HttpContext.Current.Request.ServerVariables[k]
        //                    select new XElement("item",
        //                                        new XAttribute("name", k),
        //                                        new XElement("value",
        //                                                     new XAttribute("string", v)));
 
 
 
        //    var xdoc = new XElement("error",
        //                            new XAttribute("host", System.Environment.MachineName),
        //                            new XAttribute("type", logEvent.Exception == null ?
        //                                              "" : logEvent.Exception.ToString()),
        //                            new XAttribute("message", logEvent.FormattedMessage),
        //                            new XAttribute("source", logEvent.LoggerName),
        //                            new XAttribute("detail", logEvent.StackTrace == null ?
        //                                                      "" : logEvent.StackTrace.ToString()),
        //                            new XAttribute("time", time.ToString("s") + "Z"),
        //                            new XElement("eventData", eventData),
        //                            new XElement("serverVariables", variables));
 
        //    var user = string.Empty;
 
        //    if (HttpContext.Current != null && HttpContext.Current.User.Identity.IsAuthenticated)
        //    {
        //        user = HttpContext.Current.User.Identity.Name;
        //    }
 
        //    var cmd = new SqlCommand("ELMAH_LogError", connection);
        //    cmd.CommandType = CommandType.StoredProcedure;
        //    cmd.Parameters.Add("ErrorId", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid();
        //    cmd.Parameters.Add("Application", SqlDbType.NVarChar).Value = application;
        //    cmd.Parameters.Add("Host", SqlDbType.NVarChar).Value = System.Environment.MachineName;
        //    cmd.Parameters.Add("Type", SqlDbType.NVarChar).Value = "NLog-" + logEvent.Level.Name;
        //    cmd.Parameters.Add("Source", SqlDbType.NVarChar).Value = logEvent.LoggerName;
        //    cmd.Parameters.Add("Message", SqlDbType.NVarChar).Value = logEvent.FormattedMessage;
        //    cmd.Parameters.Add("User", SqlDbType.NVarChar).Value = user;
        //    cmd.Parameters.Add("AllXml", SqlDbType.NVarChar).Value = xdoc.ToString();
        //    cmd.Parameters.Add("StatusCode", SqlDbType.Int).Value = 0;
        //    cmd.Parameters.Add("TimeUtc", SqlDbType.DateTime).Value = time;
 
        //    cmd.ExecuteNonQuery();
        //}
    //} 
}

第二步,在 NLog 的設定檔改設定

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <extensions>
    <add assembly="NlogElmah" />
  </extensions>
  
  <targets>
    <target name="elmah" xsi:type="Elmah" connectionStringName="elmahDB" />
  </targets>
 
  <rules>
    <logger name="*" minlevel="Trace" writeTo="elmah" />
  </rules>
</nlog>

這樣就大功完成了,NLog 的設定檔也非常簡單,沒有啥需要設定的,因為都寫在 code 裡面了 XD…

我們來看一下結果,看是不是有如想像中的結果..

clip_image001
clip_image002

很好,如我所想像的,這樣可以很簡單的去把 NLog 的結果讀出來,有問題再一起討論吧..^^..收工!!

參考:
http://nlog-project.org/wiki/How_to_write_a_Target
http://bibby.be/2011/11/bibbyelmahmssqlmssqlxmlxmlxmlxmlelmahsq.html

星期二, 8月 07, 2012

分頁

最近看到專案,裡面的分頁都是用內建的方法來做分頁(資料都塞到 viewstate),每個 grid 抓出來的資料都是上百筆甚至還有上千筆的,效能都是干我屁事,完全沒有考慮品質的問題,把責任都丟給了區網,還有那傳來傳去累到死 browser 跟 iis,如果在資料少的情況下,這樣寫還可以說得過去,如果資料很多那就是管他媽媽嫁給誰的您老師咖好的寫法..

不過換個想法,在 asp.net 裡面,不把資料塞在 viewstate,要多少資料拿多少資料的分頁寫法,很困難嗎?不然大家怎麼會用這個"內建"的寫法,去處理大量資料的問題,所以當然要股溝谷歌大神 survey 一下,看看 asp.net 有啥好的解決方案

[C#] 使用 ObjectDataSource 實作資料來源分頁
[SubText心得]ASP.NET TSQL 效能較佳分頁方法及友善 SEO 的分頁

上面是網路找到的效率好的分頁處理方式,可以參考一下,不過好像不是這麼簡單,在印象中應該不會這麼困難,所以 Bibby 我就自己做做看,看到底分頁可不可以簡單寫!

第一步,懶惰是程式人的進步動力,知道站在巨人的肩膀上,才會飛的高飛的遠(懶就懶都是多說低),所以就去找了一個 thrid party 的分頁元件,可以用 nuget 來下載

AspNetPager paging control
http://en.webdiyer.com/AspNetPager

clip_image001

第二步,把元件加入 Toolbox Panel

clip_image002

clip_image003

clip_image004

clip_image005

clip_image006

第三步,直接 coding 啦,這裡我是用 northwind.mdb 和 entityframework 來做範例

.aspx(把控制項拉到頁面上)

<%@ Page Language="C#" AutoEventWireup="true" 
CodeBehind="Pager.aspx.cs" Inherits="WebApplication1.Pager" %>


<%@ Register Assembly="AspNetPager" Namespace="Wuqi.Webdiyer" TagPrefix="webdiyer" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:GridView ID="GridView1" runat="server">
    </asp:GridView>
    <webdiyer:AspNetPager ID="AspNetPager1" runat="server">
    </webdiyer:AspNetPager>
    </div>
    </form>
   
</body>
</html>


.cs

    public partial class Pager : System.Web.UI.Page
    {
        private int PageSize { get { return 8; } }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            AspNetPager1.PageChanged += new EventHandler(AspNetPager1_PageChanged);
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
                InitForm();
        }

        void AspNetPager1_PageChanged(object sender, EventArgs e)
        {
            var pager = sender as AspNetPager;
            BindData(pager.CurrentPageIndex);
        }

        private void InitForm()
        {
            BindData(1);
        }

        private void BindData(int currentIndex)
        {
            //GridView
            GridView1.DataSource = GetGridViewData(currentIndex);
            GridView1.DataBind();

            //Pager
            AspNetPager1.RecordCount = GetAllData().Count();
            AspNetPager1.CurrentPageIndex = currentIndex;
            AspNetPager1.PageSize = PageSize;
        }

        private List<Products> GetGridViewData(int currentIndex)
        {
            int cIndex = currentIndex - 1;
            return GetAllData().OrderBy(a => a.ProductID)
                             .Skip(cIndex * PageSize)
                             .Take(PageSize).ToList();
        }

        private IQueryable<Products> GetAllData()
        {
            NorthwindEntities db = new NorthwindEntities();
            return db.Products;
        }
    }


畫面

clip_image007

這是到這邊,很初步的寫完了,其實 code 並沒有很多,這寫法的優點是,把分頁跟 grid 處理分開了,也沒有牽扯到 store procedure,所以哪一天你覺得 gridview 不好用,想換成 listview 或是 datalist 就直接換掉,沒有啥需要好考慮的,如果想要用 ado.net 來做到相同的效果,應該也不會有太大的問題,只是換換 method 而已,所以..該會的還是要會,品質該顧的還是要顧,出來混的總有一天要還的!!

PS:這 AspNetPager paging control 的控制項有一堆可以調的東西,可以調的很漂亮,範例醜醜的大家忍耐一下

參考:
http://en.webdiyer.com/AspNetPager
http://en.webdiyer.com/AspNetPagerDemo/

星期五, 7月 27, 2012

密碼產生器

今天業主跟我說,他希望 user 密碼不要自己設,因為 user 設密碼都用超級簡單的,例如:身分字號、電話號碼、生日等等..這樣哪天被惡搞,就很倒楣,所以我就跟客戶建議說,那寫支密碼產生器好了,讓我們來幫客戶設,我們把規則設定好,這樣有每個用戶就都會有很難記的密碼啦!XD..

不過難記歸難記,總不能連打都不知怎麼打吧!所以討論規則如下:

1.要有小寫英文
2.要有大寫英文
3.要有數字
4.密碼長度可以業主自己設定

上面這些條件,應該是地球人可口述也可以勉強記起來的規則..所以,噹噹噹..開工!

public class PasswordCreator
{
    Random r = new Random();
    string[] strArr = {
        "abcdefghijklmnopqrstuvwxyz",
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
        "0123456789"
    };
    
    public string GetPassword(int passwordLength)
    {
        //strArr.Sum(a=>a.ToCharArray().Count()).Dump();
                        
        if(strArr.Sum(a=>a.ToCharArray().Count())< passwordLength)
            throw new Exception("密碼長度太長");
            
        if(passwordLength<strArr.Count())
            throw new Exception("密碼長度太短");
        
        if(strArr.Count()==0)
            throw new Exception("密碼條件規則不許為空");
    
        int partLength = (passwordLength / strArr.Count()) 
                          + ((passwordLength % strArr.Count()) == 0 ? 0 : 1);
        
        var finialArr = new List<char>();
        foreach(var item in strArr)
            finialArr.AddRange(GetCharArr(item, partLength));            
        var randomChars = GetCharArr(string.Join("", finialArr),finialArr.Count())
                                .Take(passwordLength).ToArray();
        return    string.Join("",randomChars);    
    }
    
    private IEnumerable<char> GetCharArr(string str, int number)
    {        
        return str.ToCharArray()
                    .OrderBy(a => r.Next())
                    .Take(number);
    }
}

用法

void Main()
{
    var pc = new PasswordCreator();
    Console.WriteLine(pc.GetPassword(8));
    Console.WriteLine(pc.GetPassword(6));
    Console.WriteLine(pc.GetPassword(4));
    Console.WriteLine(pc.GetPassword(3));
    Console.WriteLine(pc.GetPassword(15));
}

結果

bSIg7aQ3
wo9JS4
5wEr
9qQ
gWH0fO3IpPr912a

上面這段程式,花一點點時間寫的,沒有很嚴謹的測試,有 bug 記得要自己改啊,還有如果需要有更"困難"的字元,請自己加入就行啦,程式也不用啥改,各位大大如果有需要就拿去參考吧!記得看懂再用XD

PS:幫客戶設密碼,是有副作用的,就是...user 不會記密碼,會一直問你,然後你(業主)就要幫 user 重新設定密碼,這一點記得要跟業主打預防針啊!!!!!!

參考:
http://stackoverflow.com/questions/1287567/c-is-using-random-and-orderby-a-good-shuffle-algorithm

星期一, 7月 16, 2012

ASP.NET MVC - 新增、查詢、修改、刪除基本功實戰

這裡先謝謝大家那天的光臨參與,讓這次的研討會順利成功,不過有些不足的地方需要大家多多擔待,例如場地因素以及經驗上時間掌握的不足,讓課程講的有點急促,這些我都會加以檢討的。

本次的內容主要是講解 ASP.NET MVC 如何去對資料庫的新增、查詢、修改、刪除等這些基本的動作,這些動作都屬於很基本的東西,大家應該把這些動作練習到很熟悉,學會了這些內容,ASP.NET MVC 就已經會了 6 成以上了,之後再經過一些專案的磨練,應該不久就會有小成了,這裡也把當天的 PPT 及範例分享出來,讓大家可以下載學習!

研討會內容:
http://mvc.tw/Event/2012/7/14

投影片:
https://speakerdeck.com/u/twmvc/p/aspnet-mvc-2

範例檔案:
http://dl.dropbox.com/u/76162656/d/%32%30%31%32%30%37%31%34%5f%42%69%62%62%79%5f%4d%56%43%5f%43%52%55%44%53.zip

星期三, 1月 25, 2012

Lose the windows phone template in visual studio 2010

今天在安裝 windows phone 7.1 的 sdk,遇到了一個問題,就是安裝完後在 visual studio 2010 找不到 windows phone 的 template,網路搜尋這個問題,這裡整理一下解決方法,讓有遇到問題的人,也能很快的把問題解決掉

1.先到路徑 Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ProjectTemplates\CSharp\Silverlight for Windows Phone 找 template,如果沒有這個 folder,那 sdk 應該沒裝成功,再裝一次吧!

2.確認你的 visual studio 的語言版本,如果是英文就把把 template folder 的改成 1033 如果是中文改成 1028,建議留下原來的用複製的方式來改檔案名

clip_image001

3.把 visual studio command prompt 的叫出來執行 devenv /installvstemplates,如果有權限問題,請用administrator來執行

clip_image002

這樣 template 就出來啦!

clip_image003

有問題再討論吧!!

參考:
http://stackoverflow.com/questions/7313376/visual-studio-2010-lost-the-wp7-templates