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

Welcome to Pylons

ลง Pylons ใช้มันก็ง่ายๆครับ ก่อนอื่นลง Setuptools ให้เรียบร้อยก่อน ใครใคร่ลงแบบไหนก็ตามสบาย ตัวผมเองเลือกลงแบบ virtual python ลองทำดูไม่ยากอะไร

พอลง setuptools เสร็จก็ได้เวลาลง pylons ด้วยการพิมพ์

easy_install Pylons==0.9.6.2

เพื่อบอกว่าจะเอา 0.9.6.2 มา แต่ถ้าไม่ระบุเวอร์ชันก็จะได้ล่าสุด ณ ปัจจุบันคือ 0.97rc1 มาแทน
รอซักพัก พอเห็นพร้อมท์ขึ้นมาอีกทีก็แปลว่าลงเสร็จแล้ว

ลองสร้าง Project ดูด้วย paste

paster create -t pylons helloproject

ตอบคำถามเรื่อง template กับ SQLAlchemy ผ่านก็จะได้ project มาหนึ่งอันชื่อ helloproject

ไม่ต้องพูดพร่ำทำเพลง เปิดขึ้นมารันซะ

cd helloproject
paster serve –reload development.ini

ถ้ารันได้ก็ลองวิ่งไปที่ http://localhost:5000/ ต้องเห็นหน้าตาประมาณนี้
Pylons
(รูปจาก vee’s blog)
ต่อไปสร้าง controller ด้วยคำสั่ง

paster controller hello

แล้วลองเข้า http://localhost:5000/hello/index ดู ต้องมีคำว่า Hello World ปรากฏ

Hello world

ก็ง่ายๆแค่นี้แหละ
ด้วยรักและไพลอนส์

Flickr ASP.NET MVC app pt.2 – ajaxified

After we have finished the Flickr MVC app last time. This time will be sequel, ajaxified it.

Although the ASP.NET MVC do provide the abstract ajax helper class, only implementation exists publicly is Nikhil’s. Certainly, ASP.NET Team will include the ASP.NET AJAX/Microsoft AJAX Library helper in the MVC framework for next CTP bits. As for this tutorial, I will continue with jquery instead of Microsoft AJAX Library because I’m more familiar with it.

The ajaxified instructions is mainly related to the view. In order to use jquery, put the library in ~/Content and add the reference to it in the layout.

<head runat="server">
    <title>My Sample MVC Application</title>
    <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="../../Content/jquery.js"></script>
</head>

Next, Open the ~/Views/Home/Index.aspx. Then, add a placeholder div called “result” in the view. We’ll use this div to display the response from the ajax call. Additionally, add a eye-candy loading indicator to let user knows that the page is being loaded.

<%Html.Form("Search", "Home", FormExtensions.FormMethod.post, new { ID = "theform" });%>
    <label for="tags">
        Tags:
    </label>
    <%=Html.TextBox("tags", 30) %>
    <%=Html.SubmitButton("find", "Find", "") %>
    <img src="../../Content/loading.gif" id="spinner" style="display:none;" />
    </form>
<div id="result"></div>

Last but not least, add some lines of script to make an ajax call once the form is goin to be submitted.

 $(document).ready(function() {        
        $('#theform').submit(function (){            
            $.post('/Home/Search', {'tags':$('#tags')[0].value}, completeRequest);
            $('#spinner').show();                        
            $('#result').slideUp(1500);
            return false;            
        });
    });
    
    function completeRequest(r){            
        $('#result').html(r).slideDown(1500);                            
        $('#spinner').hide();        
    }

The script will be invoked when the DOM is ready. It’ll hook the form submit event, post the textbox value to ~/Home/Search, make the loading indicator visible, hide the result div and then, cancel the event to prevent the full post process. And then, after the request is completed, completeRequest function will be called. It will replace the div’s content with the result from the response and then reveal the div by using slide down effect. And lastly, hide the indicator.

Try the page and Voila! The ajaxified is completed.

Compare to the traditional web form, MVC has a better degree to integrate with the javascript library. Though I use the web form view engine, extension method on the HTML Helper that makes the markup more flexible, no generated id. However, integration with the jquery may not be the easiest one for ASP.NET MVC as ASP.NET Team will deliver its own Ajax helper.

In conclusion, AJAX in ASP.NET MVC has a lot of room for improvement, either officially or unofficially. ASP.NET AJAX becomes useless when using it with ASP.NET MVC. However, using the alternative libraries in MVC require less effort than the traditional way. jQquery and ExtJs are the most notable. Besides, mootools and YUI are also gaining their share. Anyway, Microsoft will push ASP.NET AJAX little ahead those 3rd party library through their Ajax Helper once again.

ASP.NET MVC Ajax

After the long weekend, Scott announced the ASP.NET 3.5 Extension which includes the preview version of long-waited MVC Framework, ASP.NET MVC. Today, there are many websites and blogs regarding ASP.NET MVC. However, just a few of them are talking about AJAX.

In Web form model of ASP.NET, ASP.NET AJAX, along with AJAX Control Toolkit, is the major player for AJAX library. Since ASP.NET AJAX relies on Web form, migrating it to use with ASP.NET MVC which is just a beta bit is not really worth. But in the end, Microsoft will push its AJAX product towards ASP.NET MVC as soon as the latter is ready for production. This can be seen in Nikhil’s post about AJAX in ASP.NET MVC. Though Nikhil uses his Script# to create the javascript library, the concept utilizes the behavior/component things which is the concept of ASP.NET AJAX/AJAX Control toolkit. No doubt that ASP.NET AJAX will be the one that shipped with ASP.NET MVC.

As for alternative, jQuery seems to be the winner at the time of writing. Moreover, The latest survey reveals that the most favorite alternative javascript library among ASP.NET developer is jQuery and I don’t think ASP.NET MVC will differs from its counterpart. jQuery, in fact , is the fourth but the first two places are in paired, the second actually is not the javascript library so I count jQuery as the most favorite one. However, mootools, prototype/script.aculo.us will gaining their share in MVC as many people will go to ASP.NET for its MVC.

%d bloggers like this: