ASP.NET MVC 4 實作 取得ModelState Error 錯誤訊息

有了功能卻沒有能阻擋錯誤的資料寫入資料庫中,這會是很嚴重的一件事情。但後端驗證該怎麼做到?
於是乎找到了「[ASP NET MVC] 透過AJAX接收ModelState Errors顯示於對應欄位中」Chirs前輩所寫的文章,將其方法應用在我們這次建立的專案之中。

那麼它是透過什麼方式來驗證?
透過Model驗證,加上jquery.validate來做前端的接收和顯示。
廢話不多說,就用實際做一遍就了解了。

 

上一篇文章中,我們寫好了整個Create的功能後,要加入驗證要做一些修改。

先從後端製作出要給前端的訊息來開始,首先我們先把Model延伸出MetaData來寫入Model驗證,也能在此設定Display Name等等的相關訊息,但這不是此篇的重點。
我們的Model叫做Customers所以我們就寫一個CustomersMetadata出來對照他,程式碼如下。
 

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;


namespace Ajax_CRUD_Model
{
    [MetadataType(typeof(CustomersMetadata))]
    public partial class Customers
    {
        public class CustomersMetadata
        {

            [Required]
            [Display(Name = "客戶代號")]
            public string CustomerID { get; set; }

            [Required]
            [Display(Name = "客戶名稱")]
            public string CompanyName { get; set; }

           
            [Display(Name = "聯絡窗口")]
            public string ContactName { get; set; }

            
            [Display(Name = "職務名稱")]
            public string ContactTitle { get; set; }

            [Display(Name = "地址")]
            public string Address { get; set; }

            [Display(Name = "所在城市")]
            public string City { get; set; }

            [Display(Name = "地區")]
            public string Region { get; set; }

            [Display(Name = "郵遞區號")]
            public string PostalCode { get; set; }

            [Display(Name = "國家")]
            public string Country { get; set; }

            [Display(Name = "連絡電話")]
            public string Phone { get; set; }

            [Display(Name = "傳真")]
            public string Fax { get; set; }

        }
    }
}

寫完可以先執行一次確認是否有參照到此Metadata,從我們的DataTable去看Title的部分有沒有連動到就知道了。畫面如下。
在Metadata中有加入[Required]去驗證此欄位必填。我們後續會去驗證怎麼接收這個Model的部分。

Model驗證的部分都設定好後,接下來就是要寫Controller的部分。原先的程式碼如下。
 

[HttpPost]
public ActionResult Create(Customers customers)
{
   if (customers != null && ModelState.IsValid)
   {
      this.custromService.Create(customers);
      var returnData = new
      {
         // 成功與否
         IsSuccess = true,
      };
      return Content(Newtonsoft.Json.JsonConvert.SerializeObject(returnData), "application/json"); 
   }
   else
   {
      var returnData = new
      {
         // 成功與否
         IsSuccess = false,
      };

      return Content(Newtonsoft.Json.JsonConvert.SerializeObject(returnData), "application/json"); 
   }
}

把它修改成下面。
 

[HttpPost]
public ActionResult Create(Customers customers)
{
    var isSuccess = true;
    var returnData = new
    {
        // 成功與否
        IsSuccess = isSuccess,
        // ModelState錯誤訊息
        ModelStateErrors = ModelState.Where(x => x.Value.Errors.Count > 0)
        .ToDictionary(k => k.Key, k => k.Value.Errors.Select(e => e.ErrorMessage).ToArray())
    };

    if (customers != null && ModelState.IsValid)
    {
        var message = this.custromService.Create(customers);

        if (message != null) 
        {
            isSuccess = false;
            returnData = new
            {
                // 成功與否
                IsSuccess = isSuccess,
                // ModelState錯誤訊息
                ModelStateErrors = ModelState.Where(x => x.Value.Errors.Count > 0)
                .ToDictionary(k => k.Key, k => k.Value.Errors.Select(e => e.ErrorMessage).ToArray())
            };
            return Content(Newtonsoft.Json.JsonConvert.SerializeObject(returnData), "application/json"); 
        }

        return Content(Newtonsoft.Json.JsonConvert.SerializeObject(returnData), "application/json");
    }
    else
    {
        isSuccess = false;
        returnData = new
        {
            // 成功與否
            IsSuccess = isSuccess,
            // ModelState錯誤訊息
            ModelStateErrors = ModelState.Where(x => x.Value.Errors.Count > 0)
            .ToDictionary(k => k.Key, k => k.Value.Errors.Select(e => e.ErrorMessage).ToArray())
        };
        return Content(Newtonsoft.Json.JsonConvert.SerializeObject(returnData), "application/json"); 
        }
    }

ModelStateErrors是去抓取ModelState所蒐集的錯誤訊息,然後再加入returnData內回傳給前端。在「[ASP NET MVC] 透過AJAX接收ModelState Errors顯示於對應欄位中」Chirs前輩中都有介紹到,ModelState的錯誤訊息是如何抓取,有興趣的朋友可以在此篇找到答案。

如果有些時候在後端Model驗證不夠做使用,要自訂自己的回傳錯誤訊息,可以利用

ModelState.AddModelError("MyErrorMessage", "SystemErrorMessage");

然後透過與Controller同樣的方式去抓取到加入回傳的資料中,再回傳給前端即可。

最後還是要給前端去做顯示,所以我們要來修改前端的jQuery的部分。原程式碼如下。

<!--Ajax Create-->
<script>
    function Create() {
        var myForm = $("#CreateForm");
        var dataForm = myForm.serialize();
        var validator = myForm.validate();

        $.ajax({
            type: 'POST',
            url: '@Url.Action("Create", "Customer")',
            data: dataForm,
            cache: false,
            async: false,
            dataType: 'json',
            success: function (data) {
              
                    $('#Create').modal('hide');
                    alert('新增資料成功');
                    var page = window.location.hash
                    ? window.location.hash.slice(1)
                    : 1;
                    fetchPage(page);
            }
        });
    }
</script>

修改後如下。
 

<!--Ajax Create-->
<script>
    function Create() {
        var myForm = $("#CreateForm");
        var dataForm = myForm.serialize();
        var validator = myForm.validate();

        $.ajax({
            type: 'POST',
            url: '@Url.Action("Create", "Customer")',
            data: dataForm,
            cache: false,
            async: false,
            dataType: 'json',
            success: function (data) {
                if (data.IsSuccess) {
                    $('#Create').modal('hide');
                    $('#CreateForm')[0].reset();
                    alert('新增資料成功');
                    var page = window.location.hash
                    ? window.location.hash.slice(1)
                    : 1;
                    fetchPage(page);
                }
                else
                {
                    if ($.isEmptyObject(data.ModelStateErrors == false)) {
                        //show model state error
                        validator.showErrors(data.ModelStateErrors);
                    }
                    else
                    {
                        alert('ErrorMessage');
                    }
                }
                
            }
        });
    }
</script>

validator會將對應的ModelStateErrors對應到相對的欄位中。

逐步執行就可以發現,Metadata所制定的Modle驗證中會產生一個對應的錯誤資料,如下;

而jQuery validator會產生一個對應的Label去對應該錯誤訊息,如下;


可以發現這些透過一個去對應到所有的資訊才能串起來。欄位的ID

也就是在表單輸入的 Model  的資料欄位  = Metadata 設定的對應資料 = Textbox id  = validator 產生的 label id

這些地方都是互相關聯的,要特別注意到。

那麼要讓validator顯示有些自訂出來的ModelState.Error呢? 請參考下面的程式碼。

 

isSuccess = false;

ModelState.AddModelError("TestError", "TestErrorMessage");

returnData = new
{
 // 成功與否
 IsSuccess = isSuccess,
 // ModelState錯誤訊息
 ModelStateErrors = ModelState.Where(x => x.Value.Errors.Count > 0)
 .ToDictionary(k => k.Key, k => k.Value.Errors.Select(e => e.ErrorMessage).ToArray())
};
return Content(Newtonsoft.Json.JsonConvert.SerializeObject(returnData), "application/json"); 

ModelState.AddModelError ->自訂的錯誤訊息,然而ModelStateErrors也會將這自訂的錯誤訊息加入回傳的內容中。
但我們沒有設定對應的位置給他去產生訊息,而我們希望他以警告視窗顯示該錯誤訊息,在jQuery該如何取得此自訂的錯誤訊息,就參考下面的代碼。

alert(data.ModelStateErrors.TestError);

最後面的TestError就是錯誤訊息的名稱,就可以取得該錯誤訊息的內容,顯示在警告視窗上面。



以上就是本篇想記錄的內容,如有更好方法或者有比較差的地方歡迎各位前輩指點出來,謝謝。