2014年11月12日 星期三

MVC初體驗

一、新增MVC專案
 












題外話: 作者一開始並未勾選Web API, 結果使用Web API發生無法預期錯誤,只好建立新的專案,重練一次!















PS:注意 新建專案勾選web api和新增單元測試

測試單元是一個很有趣的議題,不過不在這次討論範圍喔~



一、建立版型
透過Bootstrap 進行佈景主題,許多文章也對此議題多做說明,我就不再多做說明

 <body >
        <header>
            <div class="content-wrapper">
                <div class="float-left">
                    <p class="site-title"> @Html.ActionLink( "your logo here", "Index" , "Home") </p>
                </div>
                <div class="float-right">
                    <nav>
                        <ul id="menu">
                            <li> @Html.ActionLink( "新增", "Index" , "Home") </li>
                            <li> @Html.ActionLink( "About", "About" , "Home") </li>
                            <li> @Html.ActionLink( "Contact", "Contact" , "Home") </li>
                        </ul>
                    </nav>
                </div>
            </div>
        </header>
        <div id="body">
            @RenderSection("featured", required: false)
            <section class="content-wrapper main-content clear-fix">
                @RenderBody()
            </section>
        </div>
        <footer>
            <div class="content-wrapper">
                <div class="float-left">
                    <p> &copy; @DateTime.Now.Year - My Telerik MVC Application </p>
                </div>
            </div>
        </footer>
    </body >



二、建立首頁

作者有使用Kendo UI的第三方套件,順便來看看如何運用在MVC的架構上

@model  :
 MVC架構中的Model 是實體資料庫的物件,透過@model 可直接把資料顯示在畫面上, 例如 @Html.DropDownListFor(m => m.targetType, Model.targetTypeValues) 使用到messageCenter.Models.entityMessage targetTypeValues

Html.BeginForm("Index", "Home", FormMethod.Post)
    如同早期語法<form method=’post’ action=’Home/index’> 按下Submit 後轉址 Home/Index

Html.Kendo().DateTimePickerFor: 選擇日期或時間的下拉式列表
 .value(): 設定預設值
  
@Html.Kendo().EditorFor : 強大文字編輯方塊

設定預設值: .value()
上傳圖片路徑:
ImageBrowser(imageBrowser => imageBrowser
                        .Image("~/Content/UserFiles/Images/{0}")
                        .Read("Read", "ImageBrowser")
                        .Create("Create", "ImageBrowser")
                        .Destroy("Destroy", "ImageBrowser")
                        .Upload("Upload", "ImageBrowser")
                        .Thumbnail("Thumbnail", "ImageBrowser"))
編輯文字方塊的功能
.Tools(tools => tools
                        .Clear()  // 清空
                        .Bold().Italic().Underline().Strikethrough()  // 粗體,鈄體,
                        .JustifyLeft().JustifyCenter().JustifyRight().JustifyFull() //字左,字中…
                        .InsertUnorderedList().InsertOrderedList()
                        .Outdent().Indent()
                        .CreateLink().Unlink()
                        .InsertImage() //插入圖片
                        .InsertFile()
                        .SubScript()
                        .SuperScript()
                        .TableEditing()
                        .ViewHtml()  //檢示html 語法
                        .Formatting()
                        .CleanFormatting()
                        .FontName()
                        .FontSize()
                        .FontColor().BackColor()
                        )



@(Html.Kendo().Window() : 彈跳視窗

  .Name("window")
            .Title("Rams's Ten Principles of Good Design")
            .Content("loading user info...")
            .LoadContentFrom("DetailPage", "Home") //讀取頁面
            .Iframe(true//是否IFrame
            .Draggable()  //是否拖移視窗
            .Resizable()  //改變大小
            .Width(800).Height(200) //視窗大小


各位有注意到@(Html.Kendo().DateTimePickerFor(m=>m.validateDatetime)
的語法,是設定model的欄位和view 之間的繫結, 資料post送到後端,就直接對應物件的屬性,十分的彈性。

其他關於Razor 語法,詳細文請見 http://ithelp.ithome.com.tw/question/10160185?tab=opinion

@model messageCenter.Models.entityMessage
@using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
    <div class="body-content">
        <label>
            有效日期
        </label>
        <div id='to-do'>          
            @(Html.Kendo().DateTimePickerFor(m=>m.validateDatetime)
                    .Value(DateTime.Now)
            )
        </div>
        <div class="form-group">
            <label>訊息名稱 @Html.ValidationMessageFor(model => model.message)</label>&nbsp;&nbsp;
                        @Html.Kendo().EditorFor(m => m.message).HtmlAttributes(new
           {
               style = "min-width:100%;width: 740px;height:440px"
           }).Deferred(false).ImageBrowser(imageBrowser => imageBrowser
                        .Image("~/Content/UserFiles/Images/{0}")
                        .Read("Read", "ImageBrowser")
                        .Create("Create", "ImageBrowser")
                        .Destroy("Destroy", "ImageBrowser")
                        .Upload("Upload", "ImageBrowser")
                        .Thumbnail("Thumbnail", "ImageBrowser")).Tools(tools => tools
                        .Clear()
                        .Bold().Italic().Underline().Strikethrough()
                        .JustifyLeft().JustifyCenter().JustifyRight().JustifyFull()
                        .InsertUnorderedList().InsertOrderedList()
                        .Outdent().Indent()
                        .CreateLink().Unlink()
                        .InsertImage()
                        .InsertFile()
                        .SubScript()
                        .SuperScript()
                        .TableEditing()
                        .ViewHtml()
                        .Formatting()
                        .CleanFormatting()
                        .FontName()
                        .FontSize()
                        .FontColor().BackColor()
                        )
        </div>
        <p></p>
        <div class="form-group">
            <label>
                發佈類別
                @Html.DropDownListFor(m => m.targetType, Model.targetTypeValues)
            </label>
        </div>
        <div class="form-group">
            <label>
                發佈對象
                @Html.TextBoxFor(m => Model.targetUser)
                <a href="javascript:void(0)" id="hyperlinkPage">                 
                </a>
            </label>
        </div>
        @(Html.Kendo().Window()
            .Name("window")
            .Title("Rams's Ten Principles of Good Design")
            .Content("loading user info...")
            .LoadContentFrom("DetailPage", "Home")
            .Iframe(true)
            .Draggable()
            .Resizable()
            .Width(800).Height(200)
        )
        <button type="submit" class="btn btn-success" id="btnAdd">新增</button>
    </div>
}




三、建立controller

Kendo UI EditorFor 上傳圖片的功能,需要在Controller 新增 ImageBrowserController 類別,以下是上傳圖片的後端程式

public class ImageBrowserController : EditorImageBrowserController
    {
        private const string contentFolderRoot = "~/Content/";
        private const string prettyName = "Images/";
        private static readonly string[] foldersToCopy = new[] { "~/Content/shared/" };
        /// <summary>
        /// Gets the base paths from which content will be served.
        /// </summary>
        public override string ContentPath
        {
            get
            {
                return CreateUserFolder();
            }
        }

        private string CreateUserFolder()
        {
            var virtualPath = Path.Combine(contentFolderRoot, "UserFiles", prettyName);
            var path = Server.MapPath(virtualPath);
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
                foreach (var sourceFolder in foldersToCopy)
                {
                    CopyFolder(Server.MapPath(sourceFolder), path);
                }
            }
            return virtualPath;
        }

        private void CopyFolder(string source, string destination)
        {
            if (!Directory.Exists(destination))
            {
                Directory.CreateDirectory(destination);
            }

            foreach (var file in Directory.EnumerateFiles(source))
            {
                var dest = Path.Combine(destination, Path.GetFileName(file));
                System.IO.File.Copy(file, dest);
            }

            foreach (var folder in Directory.EnumerateDirectories(source))
            {
                var dest = Path.Combine(destination, Path.GetFileName(folder));
                CopyFolder(folder, dest);
            }
        }


要瞭解Controller 的運作模式,早期web form view的事件視為後端的方法,然而在MVC中完全脫離這種模式,改以APP_Start\ RouteConfig.cs註冊的路由表,把資料送到對應的view

透過http呼叫的協定(get,post, delete,put),來決定Action ,所以當按下確定送出submit,呼叫與view對應的Action,而且表頭有[HttpPost]
 
      [HttpPost]
        public ActionResult Index(entityMessage entityMessageObj)
        {
        }




範例:

網址: http://localhost:50135/Home/index


按下新增之後,會對應到有HttpPostindexAction,Action負責呼叫資料層的物件,呼叫Index(entityMessage entityMessageObj) Action

    public class HomeController : Controller
    {
        entityMessage entityMessageModel = new entityMessage();
        public ActionResult Index()
        {
            ViewBag.Message = "";
            return View(entityMessageModel);
        }

        [HttpPost]
        public ActionResult Index(entityMessage entityMessageObj)
        {
            if (ModelState.IsValid)
            {
                ViewBag.Message = "";
                //轉成html語法
                entityMessageObj.message = HttpUtility.HtmlDecode(entityMessageObj.message);                entityMessageObj.add(); //儲存父表單和子表單
                return RedirectToAction("About");
            }
            else
            {
                return View(entityMessageModel);
            }
        }


四、建立Ajax 網頁

Ajax 網頁的運作已經非常普遍,所以此案例中,將示範如何透過controller 回傳Json格式的資料給kendo UI,達到AJAX 的應用。

前端Script 使用JQuery 語法
  $("#hyperlinkPage").html("<img src='\Images/detailForm.png' id='imageDeatailPageIcon' />");
                $("#hyperlinkPage")
                .bind("click", function () {
                    $("#window").data("kendoWindow").open(); //彈出視窗
                    var wnd = $("#window").data("kendoWindow");
                    if ($("#targetType").val() == 1) { 
                       //顯示欄位
                        var columns = [{ width: 30 },
                            { field: "索引編號" },
                            { field: "客戶名稱" },
                            { field: "客戶地址" },
                            { field: "客戶版本" },
                            { field: "客戶合約等級" },
                            { field: "郵遞區號" },
                        ];
                        showGrid("\Home/listAllCustomers", columns );
                    }
                });
  

function showGrid( url, columns)
        {
            columns[0].template = "<input type='checkbox' id='ck_selected'  class='checkbox'   />";
            var wnd = $("#window").data("kendoWindow");
            wnd.content("<center><table id='detail_table_header'><div id='grid'></div></table></center>");                       
            var dataSource = new kendo.data.DataSource({
                transport: {
                    read: {               
                        url:url, //取得對應Controler ACTION傳回資料
                        dataType: "json"
                    },
                },
                pageSize: 20, //此部份用分頁,每201
                schema: {
//此部分取回JSON資料,切注意,如無法取出資料, 確認JSON的格式 {Data: [({object1},{object2})]} ,請依實際情況作調整
                    data: "Data",  
                    total: "Total"
                }
            });

            $("#grid").kendoGrid({
                dataSource: dataSource, //取得JSON
                height: 550,
                dataBound: onDataBound,
                groupable: true,
                sortable: true,
                pageable: {
                    refresh: true,
                    pageSizes: true,
                    buttonCount: 5
                },
                filterable: true,
                serverPaging: true,
                columns: columns,
            });

            var grid = $("#grid").data("kendoGrid");
            grid.table.on("click", ".checkbox", Grid_Click);
            wnd.content($("#detail_items").html());
        }


Controller
     public ActionResult listAllCustomers(DataSourceRequest request)
        {
            try
            {
//與資料庫有緊密關係的Function , 作者都集中在MODEL 層…方更後續彈性切換
                IQueryable<dynamic> data = entityMessageModel.listAllCustomerFromTCS(request.Page);
                var result = new DataSourceResult()
                {
//取回JSON格式 Data: [{},{},{}], 取回JSON的關健字為Data
                    Data = data, 
                    Total = entityMessageModel.totalCustomersCount()
                };

                var json = JsonConvert.SerializeObject(result);
                return new MyJsonResult(json); 
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }



五、成果展示


彈出Ifram視窗, 並透過Kendo UI 讀取JSON


選擇郵遞區號,下方視窗切換成郵遞區號

點選上方列表,下方會自動顯示該筆的資料內容

    以上僅是個人的經驗,如果有觀念錯誤的地方,歡迎各位指教

沒有留言:

張貼留言