[iThome 第七屆鐵人賽 27] View相關的處理 - 框架自動增加Model Metadata @ Alan Tsai 的學習筆記|An Asp .Net Mvc Web Developer Blog

Alan Tsai 的學習筆記


學而不思則罔,思而不學則殆,不思不學則“網貸” 記錄軟體開發的點點滴滴 著重於微軟技術、網頁開發、DevOps、C#, Asp .net Mvc、Azure、AI、Chatbot、Docker、Data Science

[iThome 第七屆鐵人賽 27] View相關的處理 - 框架自動增加Model Metadata

在上一篇介紹完了什麽是Model Metadata和Mvc的Html Helper如何利用Metadata來產生開發者想要的Html內容,在這一篇將會介紹框架如何能夠提供一些基礎架設方便產生和我們view能夠對應的Model Metadata。

功能描述

在這一篇主要會介紹框架本生會定義增加Metadata資訊的部份,然後透過interface的方式,讓產生Metadata的邏輯能夠用實作interface的方式來產生影響。

介紹完框架的基礎建設之後,將會實作產生當輸入框沒有任何值的時候所顯示的placeholder資訊。也就是這個功能會自動產生WaterMark這個Metadata資訊,並且透過 EditorTemplate達到沒有值的輸入框出現placeholder的資訊。

另外也會介紹另外一個範例,如何透過Property名稱把UI以TextArea方式呈現。

ExtensibleModelMetadataProvider功能建設

首先第一步是要建立好修改ModelMetadata的基礎建設。有了這個部分,才來介紹如何實際使用。

IModelMetadataProcessor interface的定義

首先先從會實際執行內容的interface開始看起。因為每一個application的Convention可能不一樣,因此不管需要什麼Convention的動作,都是實作這個interface來達到。

先來看一下這個interface的定義:

public interface IModelMetadataProcessor
{
	void TransformMetadata(System.Web.Mvc.ModelMetadata modelMetadata,
		IEnumerable<Attribute> attributes);
}

這個interface很簡單,就是有個方法,這個方法會傳入目前產出的modelMetadata資訊和這個property所有的attribute。然後要做什麼,就看這個interface的實作要對modelMetadata 有沒有需要做任何的處理。


ExtensibleModelMetadataProvider主題建設


有了interface的定義之後,就來看ExtensibleModelMetadataProvider如何使用那個process的interface。


我們沒有必要整個處理都重寫,因此我們可以繼承DataAnnotationsModelMetadataProvider然後做一些修改。

public class ExtensibleModelMetadataProvider
        : DataAnnotationsModelMetadataProvider
{
    private readonly IModelMetadataProcessor[] metdataProcessor;

    public ExtensibleModelMetadataProvider(
        IModelMetadataProcessor[] metadataFilters)
    {
        metdataProcessor = metadataFilters;
    }

    protected override System.Web.Mvc.ModelMetadata CreateMetadata(
        IEnumerable<Attribute> attributes,
        Type containerType,
        Func<object> modelAccessor,
        Type modelType,
        string propertyName)
    {
        var metadata = base.CreateMetadata(
            attributes,
            containerType,
            modelAccessor,
            modelType,
            propertyName);

        foreach (var item in metdataProcessor)
        {
            item.TransformMetadata(metadata, attributes);
        }

        return metadata;
    }
}

可以看到,會透過constructor,由DI注入目前有使用的process。然後,先用DataAnnotationsModelMetadataProvider建立出基本的ModelMetadata之後,再把它呼叫到有註冊的process去做處理。


Autofac的註冊


上面基本上框架就定義出來了,欠缺的是要和DI Container說,我們要使用新的ModelMetadataProvider。


首先定義一個Autofac的Module,方便管理這個功能的註冊:

public class ExtensibleModelMetadataModule : Autofac.Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<ExtensibleModelMetadata.ExtensibleModelMetadataProvider>()
            .As<ModelMetadataProvider>();

	builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
               .AsImplementedInterfaces();
    }
}

當然也別忘了Global.asax需要註冊這個Module:

Builder.RegisterModule<ExtensibleModelMetadataModule>();


到這邊,整個基礎建設就準備好了,接下來來看如何應用。


應用


這邊的應用會介紹兩個:



  1. 欄位自動建立placeholder
  2. 自動產生TextArea而不是input text


欄位自動建立placeholder


這個功能有兩個部分,一個是在產生ModelMetadata的時候,有一個欄位叫做Watermark。這個欄位將會被用來放placeholder的資訊。


另外一個部分是需要寫一個EditorTemplates,這樣Watermark的值才會被用到。


Process的部分

public class PlaceholderProcess : IModelMetadataProcessor
{
    public void TransformMetadata(System.Web.Mvc.ModelMetadata modelMetadata, 
        IEnumerable<Attribute> attributes)
    {
        if (string.IsNullOrEmpty(modelMetadata.PropertyName) == false &&
                string.IsNullOrEmpty(modelMetadata.Watermark))
        {
            modelMetadata.Watermark = "請輸入" + modelMetadata.DisplayName + "...";
        }
    }
}

這邊程式碼很簡單,先判斷目前傳進來的欄位有沒有名字,再來就是有沒有被設定過WaterMark。如果欄位並且,沒有設定過WaterMark,才用預設的。


這一這邊的判斷,有判斷Watermark本身是否有值。這個的好處是,假設今天有幾個欄位和其他欄位不一樣,可能會直接用Attribute定義在Property上面,那麼就以Property的Attribute 為主。這裡的Process,只處理通用型符合Convention邏輯的內容。

View的部分


基本上就是在~Views/Shared/EditorTemplates/string.cshtml增加如下內容:

@model string
@Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue,
        new { @class = "form-control", 
			placeholder = ViewData.ModelMetadata.Watermark })

最後結果


呈現就會變成:


image



自動產生TextArea而不是input text


這個部分的功能需要先知道欄位的Convention是什麼,因此我這邊假設所有的TextArea的Property名稱都需要包含TextArea這個字,因此:

public class TextAreaByNameProcess : IModelMetadataProcessor
{
    private readonly HashSet<string> textAreaFieldNames =
            new HashSet<string>
                {
                    "textarea"
                };

    public void TransformMetadata(System.Web.Mvc.ModelMetadata modelMetadata,
                    IEnumerable<Attribute> attributes)
    {
        if (string.IsNullOrEmpty(modelMetadata.PropertyName) == false &&
            string.IsNullOrEmpty(modelMetadata.DataTypeName) &&
            textAreaFieldNames.Contains(modelMetadata.PropertyName.ToLower()))
        {
            modelMetadata.DataTypeName = "MultilineText";
        }
    }
}

這邊基本上就是在沒有設定任何DataType的情況下,並且property名字符合清單的設定,就把他的DataType設定成為"MultilineText",而Html.EditorFor,看到這個就會用TextArea來做html而不是input text。


結語


通過開發團隊的Convention和ExtensibleModelMetadataProvider,可以讓UI呈現上面更一致(因為不會因為忘記加Attribute就導致Placeholder出不來),並且減少ViewModel上面的Attribute的使用。


而使用Process作為實際的處理,讓整個要處理的邏輯變得彈性話,可以依照不同團隊的需求打造不同的處理。


有了ExtensibleModelMetadataProvier簡化一些View的工作之後,我們在下一篇來看一下現在很常用到的內容,也就是用Ajax和Server溝通可能遇到什麼問題的處理。

comments powered by Disqus