2014年10月29日 星期三

[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溝通可能遇到什麼問題的處理。


沒有留言 :

張貼留言