ASP.NET MVC

ASP.NET MVC : Controller Overview

เนื่องในโอกาสที่ ASP.NET MVC 1.0 ออกตัวเต็มมาเป็นที่เรียบร้อยแล้ว เราก็ได้โอกาสเขียนถึงเจ้า ASP.NET MVC อย่างจริงๆจังซักที แถมหลังๆมาก็ไม่ได้ใช้ ASP.NET MVC เลย คราวนี้ขอเขียนแบบ Back to basic เพื่อจะช่วยให้เห็นอะไรที่ไม่เคยเห็นบ้าง

URL Routing in ASP.NET MVC

ใน ASP.NET MVC เราสามารถออกแบบ URL ขึ้นมาแล้วส่ง request ไปยัง action ที่กำหนดไว้ได้ ทำให้ URL ของเว็บของเราไม่ขึ้นอยู่กับโครงสร้างของไฟล์ที่อยู่บนดิสก์จริงๆ โดย ASP.NET MVC อาศัย URL Routing ที่เพิ่มเข้ามาใน ASP.NET 3.5 SP1

หลังจากเราสร้าง Project Web Application ขึ้นมาแล้ว ลองเปิดดูที่ Global.asax จะเห็นว่าต่างจาก ASP.NET Web Application ธรรมดา เพราะจะมาการทำ Routing มาให้แล้ว

public class MvcApplication : System.Web.HttpApplication
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        routes.MapRoute(
            "Default",                                              // Route name
            "{controller}/{action}/{id}",                           // URL with parameters
            new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
        );
    }
    protected void Application_Start()
    {
        RegisterRoutes(RouteTable.Routes);
    }
}

กฏง่ายๆของการ Routing คือ

  • หน้าไปหลัง เริ่มทำการทดสอบจากกฏที่เพิ่มเข้าไปก่อนเป็นลำดับแรก เจออันไหนก่อนก็จะ route ไปตามนั้น
  • ใช้ {} เพื่อเก็บค่าตัวแปร เราสามารถตั้งตัวแปรสำหรับ URL ได้ เช่น /blog/{year}/{month}/{day} จะ match กับ /blog/2009/1/2 โดยจะเกิดตัวแปรของ URL ชื่อ year, month และ day ขึ้นโดยมีค่าเป็น 2009, 2 และ 1 ตามลำดับ
  • ใช้ * เพื่อระบุว่าเป็น parameter ที่ไม่ทราบจำนวน

ASP.NET Routing จะต้องมี Routing Handler ซึ่งจะต้องเป็นคลาสที่ implement interface IRouteHandler ซึ่งปกติใน ASP.NET MVC จะแอบใช้ System.Web.Mvc.MvcRouteHandler

จากด้านบน เราสามารถแปลได้ง่ายๆว่า

  • ไม่ต้องสนใจถ้า request url ที่เป็น .axd โดยส่วนที่อยู่หน้า . จะถูกเก็บไว้ในตัวแปรชื่อ request ส่วนที่เหลือทั้งหมดจะถูกเก็บใน pathInfo
  • Request URL จะถูก Route ไปยัง controller และ action โดยระบุพารามิเตอร์ชื่อ id โดยอัตโนมัติหากไม่มีการกำหนดค่าใดๆ

เรามาลองสร้าง Flickr Mashup ขึ้นมาเล่นๆดูดีกว่า

ก่อนอื่นสร้าง PhotoController ขึ้นมาใหม่ โดยมีโค้ดดังนี้

public class PhotoController : Controller
{        
    public ActionResult Index()
    {
        Flickr f = new Flickr();
        Photos p = f.PhotosGetRecent(25, 1);            
        return View(p.PhotoCollection);
    }
}

จากแอคชันข้างต้น เราดึงรายละเอียดของรูปที่ถูกอัพโหลดขึ้นไปไว้ใน Flickr จำนวน 25 รูปแล้วเก็บไว้ใน ViewData โดยมีใช้ โดยใช้ View ง่ายๆ ชื่อ Index คือ

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MvcApplication1.Views.Photo.Index" %>
<!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>
<title>MVC Flickr Sample</title>
</head>
<body>
    <div>
    <% foreach (var p in ViewData.Model)
       { %>                     
       <a href="<%= p.WebUrl %>">
       <img src="<%= p.SquareThumbnailUrl %>" alt="<%= p.Title %>" />                  
       </a>
    <% }%>
    </div>
</body>
</html>

โดยที่ View นี้เป็น Strong typed view

namespace MvcApplication1.Views.Photo
{
    public partial class Index : ViewPage<FlickrNet.Photo[]>
    {
    }
}

หลังจากเราตั้งค่า Flickr API เรียบร้อยตาม Document ก็ทดลองรันดูด้วย Url /photo/

ScreenShot001

ทีนี้เราลองเพิ่มอะไรขึ้นมาเล็กน้อยด้วยการเพิ่ม Route เข้าไปดังต่อไปนี้

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            "Search",                                              // Route name
            "search/{key}/{page}",                           // URL with parameters
            new { controller = "Photo", action = "Search", key = "", page = 1 },  // Parameter defaults
        );

        routes.MapRoute(
            "User",                                              // Route name
            "photo/{username}/{page}",                           // URL with parameters
            new { controller = "Photo", action = "PhotoByUser", page = 1 },  // Parameter defaults
        );

        routes.MapRoute(
            "Default",                                              // Route name
            "{controller}/{action}/{id}",                           // URL with parameters
            new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
        );

    }

จะเห็นว่าเราส่ง Request ไปยัง Action ใดๆ ก็ได้โดยไม่ขึ้นกับชื่อ กล่าวคือ เราจะทำการค้นหารูปหาก Request URL อยู่ในรูปแบบ search/คำค้น/หน้า และหากเป็น /photo/ชื่อผู้ใช้/หน้า ก็จะแสดงรูปของผู้ใช้คนนั้นแทนโดยเราไม่จำเป็นจะต้องสร้าง Search controller แต่อย่างใด แต่เราจะส่งไปที่ action Search และ PhotoByUser ตามลำดับ

โค้ดของ action ทั้งสองเป็นดังนี้

    public ActionResult PhotoByUser(string username, int page)
    {
        Flickr f = new Flickr();
        FoundUser u = f.PeopleFindByUsername(username);            
        if (u != null)
        {               
            Photos p = f.PeopleGetPublicPhotos(u.UserId, 25, page);                
            return View("index", p.PhotoCollection);
        }
        return View("index");
    }

    public ActionResult Search(string key, int page)
    {           
        Flickr f = new Flickr();
        PhotoSearchOptions option = new PhotoSearchOptions();
        option.Page = page;
        option.Text = key;         
        Photos p = f.PhotosSearch(option);                       
        return View("index", p.PhotoCollection);                        
    }

เมื่อเราทดสอบด้วยการเปิด URL /search/grassland/ และ /photo/wiennat จะปรากฎรูปขึ้นมาตามต้องการ

ScreenShot002

แล้วถ้าเราลองเปิดด้วย /search/grassland/ข้อความ และ /photo/wiennat/ข้อความ ล่ะ

ScreenShot004

ผลลัพธ์คือเกิด error ขึ้นเนื่องจากพารามิเตอร์ page นั้นมีค่าเป็น NULL แต่ว่าเราต้องการพารามิเตอร์ที่เป็นตัวเลข เรามีทางแก้สองทางคือ 1. กำหนดให้ประเภทพารามิเตอร์ page ให้เป็น int? 2. จำกัดรูปแบบของพารามิเตอร์ โดยกำหนด Constrain ให้รับแต่ตัวเลข เท่านั้น

    routes.MapRoute(
            "Search",                                              // Route name
            "search/{key}/{page}",                           // URL with parameters
            new { controller = "Photo", action = "Search", key = "", page = 1 },  // Parameter defaults
            new { key="[A-Za-z0-9]+", page="[0-9]*" }
        );

เมื่อผู้ใช้เข้ามาด้วย /search/grassland/ข้อความ ก็จะปรากฏข้อความที่บอกว่าไม่พบเอกสารดังกล่าวแทน

ScreenShot004

เป็นอันจบวิธีการใช้งาน URL Routing อย่างง่ายๆแล้ว สำหรับตอนหน้า เราจะมาดูว่าเราจะใช้ URL Routing ใน Web Form กันอย่างไร

สำหรับการใช้งานอื่นกรุณาทิ้งไว้ใน comment

Flickr ASP.NET MVC app pt.1

After reading many blog post about ASP.NET MVC for a while, I decide to write a simple application for better understanding. Flickr app seems to be best fit this time as it can be implemented in an hour.