2014年5月28日 星期三

Repostitory Pattern

Repository Pattern 是一個用來切割資料存取層和商業邏輯層的模式
針對資料的存取提供了基本的新增、修改、刪除、查詢等操作
返回的對象應該為IQueryable以供商業邏輯做更進一步的處理
這樣的好處是商業邏輯不直接處理資料的存取,方便之後抽換資料存取層,也方便單元測試
以下用一個簡單的留言版當例子透過ADO.NET和Entity Framework來存取資料
首先準備好留言版的資料庫,資料表就簡單的用GuestbookId和Message就好

定義一個Guestbook的Entity
namespace WebApplication1.Models
{
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;

    [Table("Guestbook")]
    public class Guestbook
    {
        [Key]
        public int GuestbookId { get; set; }
        public string Message { get; set; }
    }
}

順便定義一個DbContext
namespace WebApplication1.Models
{
    using System.Data.Entity;

    public class GuestbookContext : DbContext
    {
        public DbSet<Guestbook> Guestbook { get; set; }
    }
}

定義一個IGuestbookRepository介面
namespace WebApplication1.Repositories
{
    using System;
    using System.Linq;
    using WebApplication1.Models;

    public interface IGuestbookRepository : IDisposable
    {
        IQueryable<Guestbook> GetAll();
        Guestbook Find(int id);
        void Add(Guestbook model);
        void Edit(Guestbook model);
        void Delete(int id);
    }
}

透過EntityFramework實作一個Repository
namespace WebApplication1.Repositories
{
    using System;
    using System.Data.Entity;
    using System.Linq;
    using WebApplication1.Models;

    public class EFGuestbookRepository : IGuestbookRepository
    {
        private bool disposed = false;
        private GuestbookContext db;

        public EFGuestbookRepository()
        {
            this.db = new GuestbookContext();
        }

        public IQueryable<Guestbook> GetAll()
        {
            return this.db.Guestbook;
        }

        public Models.Guestbook Find(int id)
        {
            return this.db.Guestbook.Find(id);
        }

        public void Add(Guestbook model)
        {
            this.db.Guestbook.Add(model);
            this.db.SaveChanges();
        }

        public void Edit(Guestbook model)
        {
            this.db.Entry(model).State = EntityState.Modified;
            this.db.SaveChanges();
        }

        public void Delete(int id)
        {
            var model = new Guestbook { GuestbookId = id };
            this.db.Entry(model).State = EntityState.Deleted;
            this.db.SaveChanges();
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposed)
            {
                return;
            }

            this.disposed = true;

            if (disposing)
            {
                this.db.Dispose();
            }
        }
    }
}

透過ADO.Net實作一個Repository
namespace WebApplication1.Repositories
{
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Data;
    using System.Data.SqlClient;
    using System.Linq;
    using WebApplication1.Models;

    public class ADONetGuestbookRepository : IGuestbookRepository
    {
        private bool disposed = false;
        private SqlConnection objConn;
        private SqlCommand objCmd;

        public ADONetGuestbookRepository()
        {
            string strConn = ConfigurationManager.ConnectionStrings["GuestbookContext"].ConnectionString;
            objConn = new SqlConnection(strConn);
            objConn.Open();
            objCmd = new SqlCommand();
            objCmd.Connection = objConn;
        }

        public IQueryable<Guestbook> GetAll()
        {
            List<Guestbook> list = new List<Guestbook>();
            objCmd.CommandText = "SELECT * FROM Guestbook";
            using (SqlDataReader objDtr = objCmd.ExecuteReader())
            {
                while (objDtr.Read())
                {
                    Guestbook model = new Guestbook()
                    {
                        GuestbookId = Convert.ToInt32(objDtr["GuestbookId"]),
                        Message = objDtr["Message"].ToString(),
                    };

                    list.Add(model);
                }
            }

            return list.AsQueryable();
        }

        public Models.Guestbook Find(int id)
        {
            Guestbook model = new Guestbook();
            objCmd.CommandText = "SELECT * FROM Guestbook WHERE GuestbookId = @GuestbookId";
            objCmd.Parameters.AddWithValue("@GuestbookId", id);
            using (SqlDataReader objDtr = objCmd.ExecuteReader())
            {
                if (objDtr.Read())
                {
                    model.GuestbookId = Convert.ToInt32(objDtr["GuestbookId"]);
                    model.Message = objDtr["Message"].ToString();
                }
            }

            return model;
        }

        public void Add(Guestbook model)
        {
            objCmd.CommandText = "INSERT INTO Guestbook(Message) VALUES(@Message)";
            objCmd.Parameters.AddWithValue("@Message", model.Message);
            objCmd.ExecuteNonQuery();
        }

        public void Edit(Guestbook model)
        {
            objCmd.CommandText = "UPDATE Guestbook SET Message = @Message WHERE GuestbookId = @GuestbookId";
            objCmd.Parameters.AddWithValue("@GuestbookId", model.GuestbookId);
            objCmd.Parameters.AddWithValue("@Message", model.Message);
            objCmd.ExecuteNonQuery();
        }

        public void Delete(int id)
        {
            objCmd.CommandText = "DELETE FROM Guestbook WHERE GuestbookId = @GuestbookId";
            objCmd.Parameters.AddWithValue("@GuestbookId", id);
            objCmd.ExecuteNonQuery();
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (this.disposed)
            {
                return;
            }

            this.disposed = true;

            if (disposing)
            {
                if (objConn.State != ConnectionState.Closed)
                {
                    objConn.Close();
                }

                this.objConn.Dispose();
                this.objCmd.Dispose();
            }
        }
    }
}

透過Scaffold產生Controller和View

加入控制器


產生的Controller裡面是直接透過Entity Framework存取資料
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using WebApplication1.Models;

namespace WebApplication1.Controllers
{
    public class GuestbookController : Controller
    {
        private GuestbookContext db = new GuestbookContext();

        // GET: Guestbook
        public ActionResult Index()
        {
            return View(db.Guestbook.ToList());
        }

        // GET: Guestbook/Details/5
        public ActionResult Details(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Guestbook guestbook = db.Guestbook.Find(id);
            if (guestbook == null)
            {
                return HttpNotFound();
            }
            return View(guestbook);
        }

        // GET: Guestbook/Create
        public ActionResult Create()
        {
            return View();
        }

        // POST: Guestbook/Create
        // 若要免於過量張貼攻擊,請啟用想要繫結的特定屬性,如需
        // 詳細資訊,請參閱 http://go.microsoft.com/fwlink/?LinkId=317598。
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Include = "GuestbookId,Message")] Guestbook guestbook)
        {
            if (ModelState.IsValid)
            {
                db.Guestbook.Add(guestbook);
                db.SaveChanges();
                return RedirectToAction("Index");
            }

            return View(guestbook);
        }

        // GET: Guestbook/Edit/5
        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Guestbook guestbook = db.Guestbook.Find(id);
            if (guestbook == null)
            {
                return HttpNotFound();
            }
            return View(guestbook);
        }

        // POST: Guestbook/Edit/5
        // 若要免於過量張貼攻擊,請啟用想要繫結的特定屬性,如需
        // 詳細資訊,請參閱 http://go.microsoft.com/fwlink/?LinkId=317598。
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit([Bind(Include = "GuestbookId,Message")] Guestbook guestbook)
        {
            if (ModelState.IsValid)
            {
                db.Entry(guestbook).State = EntityState.Modified;
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(guestbook);
        }

        // GET: Guestbook/Delete/5
        public ActionResult Delete(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Guestbook guestbook = db.Guestbook.Find(id);
            if (guestbook == null)
            {
                return HttpNotFound();
            }
            return View(guestbook);
        }

        // POST: Guestbook/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(int id)
        {
            Guestbook guestbook = db.Guestbook.Find(id);
            db.Guestbook.Remove(guestbook);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                db.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}

做一點調整改用剛定義的IGuestbookRepository介面來存取資料
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using WebApplication1.Models;
using WebApplication1.Repositories;

namespace WebApplication1.Controllers
{
    public class GuestbookController : Controller
    {
        private IGuestbookRepository db;

        public GuestbookController()
        {
            this.db = new EFGuestbookRepository();
        }

        // GET: Guestbook
        public ActionResult Index()
        {
            return View(this.db.GetAll());
        }

        // GET: Guestbook/Details/5
        public ActionResult Details(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Guestbook guestbook = this.db.Find(id.Value);
            if (guestbook == null)
            {
                return HttpNotFound();
            }
            return View(guestbook);
        }

        // GET: Guestbook/Create
        public ActionResult Create()
        {
            return View();
        }

        // POST: Guestbook/Create
        // 若要免於過量張貼攻擊,請啟用想要繫結的特定屬性,如需
        // 詳細資訊,請參閱 http://go.microsoft.com/fwlink/?LinkId=317598。
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Include = "GuestbookId,Message")] Guestbook guestbook)
        {
            if (ModelState.IsValid)
            {
                this.db.Add(guestbook);
                return RedirectToAction("Index");
            }

            return View(guestbook);
        }

        // GET: Guestbook/Edit/5
        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Guestbook guestbook = this.db.Find(id.Value);
            if (guestbook == null)
            {
                return HttpNotFound();
            }
            return View(guestbook);
        }

        // POST: Guestbook/Edit/5
        // 若要免於過量張貼攻擊,請啟用想要繫結的特定屬性,如需
        // 詳細資訊,請參閱 http://go.microsoft.com/fwlink/?LinkId=317598。
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit([Bind(Include = "GuestbookId,Message")] Guestbook guestbook)
        {
            if (ModelState.IsValid)
            {
                this.db.Edit(guestbook);
                return RedirectToAction("Index");
            }
            return View(guestbook);
        }

        // GET: Guestbook/Delete/5
        public ActionResult Delete(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Guestbook guestbook = this.db.Find(id.Value);
            if (guestbook == null)
            {
                return HttpNotFound();
            }
            return View(guestbook);
        }

        // POST: Guestbook/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(int id)
        {
            this.db.Delete(id);
            return RedirectToAction("Index");
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                db.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}

要切換存取資料的Repository只要在建構式中切換建立的類別就行了
public GuestbookController()
{
    // this.db = new EFGuestbookRepository();
    this.db = new ADONetGuestbookRepository();
}

透過DI的方式注入介面切換就更靈活了
public GuestbookController(IGuestbookRepository repository)
{
    this.db = repository;
}

參考資料
IOC 控制反轉 & DI 依賴注入
Autofac 一種IOC容器

Markdown 輕量級的標記語言

Markdown是一種輕量級的標記語言,用來讓人們簡單地編寫易讀易懂的純文字檔案,然後轉換成有效的XHTML或HTML格式

Visual Studio中的Web Essentials擴充套件,已支援Markdown格式的預覽和編輯,只要副檔名為md的文字檔就行了

# 標題
標題符號:#,1~6數目的#符號對應H1~H6
H1符號:=
H2符號:-
範例:
# H1
## H2
### H3
#### H4
##### H5
###### H6

H1
===

H2
---

# 換行 段落
連續兩個空白字元會轉成換行<br />
連續兩個換行字元會轉成段落<p></p>
範例:
第一行
第二行
第三行

第二段

# 粗體 斜體
斜體符號:一個 * 或 _
粗體符號:兩個 * 或 _
使用方式:將要顯示的文字用符號包圍起來
範例:
*這是斜體文字*
_這是斜體文字_
**這是斜體文字**
__這是斜體文字__

# 符號列表
符號:* 或 + 或 - 和一個空白
範例:

* 第一行
* 第二行
* 第三行
+ 第四行
+ 第五行
+ 第六行
- 第七行
- 第八行
- 第九行

# 數字列表
符號:以數字和.和一個空白作開頭,這裡指定的數字不會是最後呈現的數字
範例:
1. 第一行
3. 第二行
5. 第三行
7. 第四行
9. 第五行
1. 第六行
1. 第七行
1. 第八行
1. 第九行

# 引用
符號:> 和一個空白,可嵌套
範例:

> 引用一句話
> 第二行
> 第三行

> > 嵌套引用
> > 第二行
> > 第三行

# 水平分割線<hr />
符號1:*  *  *
符號2:- - -
符號3:_ _ _
範例:
第一行
- - -
第二行
* * *
第三行
_ _ _

# 連結
符號:<>
符號:[]

<http://tw.yahoo.com>

[連結的顯示文字](http://tw.yahoo.com "連結的Alt文字")

也可以用定義的方式重覆使用
[Google]

[Google]: http://tw.yahoo.com "Alt"


# 圖片
符號:![]
範例:

![找不到圖片的替代文字](http://aaa.jpg "圖片的Alt文字")

也可以用定義的方式重覆使用
![找不到圖片的替代文字][img1]
[img1]: http://bbb.jpg "Alt"


# 代碼
符號:`,會轉換成<pre></pre>
範例:
```cs
public static void Main(string[] args)
{
    Console.WriteLine("Hello");
}```


2014年5月20日 星期二

IOC 控制反轉 & DI 依賴注入

IOC(Inversion of Control)中文翻譯為控制反轉,是物件導向中的一種設計原則,目的是用來減低物件之間的耦合度

先來看一段兩個物件協同工作的程式碼
public class ObjA
{
    private ObjB obj = new ObjB();

    public void SomeAction()
    {
        obj.Work();
    }
}

public class ObjB
{
    public void Work()
    {
        Console.WriteLine("objB Work");
    }
}

物件A透過物件B去完成一項工作,所以物件A之中需要明確地指定物件B的存在
也就是說在建立物件A的同時,物件B也同時建立起來了
再簡單一點的說法,沒有物件B就沒有物件A,物件A相依於物件B

這段程式碼如果是萬年不變的邏輯,其實也無所謂
但如果相依的物件需要替換的時後,來看看會發生什麼事
public class ObjA
{
    private ObjC obj = new ObjC();

    public void SomeAction()
    {
        obj.Action();
    }
}

public class ObjC
{
    public void Action()
    {
        Console.WriteLine("objC Work");
    }
}

宣告的型態要改變,建立物件的類型也要改變,有可能連呼叫的方法都要改變
如果這份程式是自已最近寫的,那可能還會有點印象知道那幾個地方要改
如果一份年久失修又或是別人寫的,那可能需要很有勇氣地用全域取代的方式來修改
所以比較好的設計方式,是針對介面去寫程式
public interface IObj
{
    void DoWork();
}

public class ObjA
{
    private IObj obj = new ObjB();

    public void SomeAction()
    {
        obj.DoWork();
    }
}

public class ObjB : IObj
{
    public void DoWork()
    {
        Console.WriteLine("objB Work");
    }
}

public class ObjC : IObj
{
    public void DoWork()
    {
        Console.WriteLine("objC Work");
    }
}

這樣的作法在抽換物件的時後,就只要變更建立物件的類型就可以了
但在程式碼中寫死建立的物件類型也是不太好的作法,寫死了就沒有變化性可言
接下來可以配合DI讓程式碼更靈活些

DI(Dependency Injection)中文翻譯為依賴注入,用來將物件相依的關系,利用一些方式從外部注入到物件中,而不必在物件中明確地建立相依物件
首先是建構式注入,也是最常用的方式
public class ObjA
{
    private IObj obj;

    public void ObjA(IObj obj)
    {
        this.obj = obj;
    }

    public void SomeAction()
    {
        obj.DoWork();
    }
}

第二種方式是屬性注入,比較適合相依的物件有需要和外部環境互動的時後使用
public class ObjA
{
    private IObj obj;

    public IObj Obj 
    {
        get
        {
            return this.obj;
        }
        set
        {
            this.obj = value;
        }
    }
        
    public void SomeAction()
    {
        if (this.obj == null)
        {
            throw new ArgumentNullException("obj", "obj is null");
        }

        obj.DoWork();
    }
}

第三種方式是參數注入
public class ObjA
{
    public void SomeAction(IObj obj)
    {
        if (obj == null)
        {
            throw new ArgumentNullException("obj", "obj is null");
        }

        obj.DoWork();
    }
}