ASP.NET Core 加強版 UIHint

  • 452
  • 0

過去在寫 ASP.NET MVC 時候常會使用 UIHint 配合 Display Template 或 Editor Template 來達到 View 的客制化跟共用,最新開始正式在實務上使用 ASP.NET Core MVC 來開發,也想要維持以前的習慣,因為預設 UIHint 的參數並無法直接在 View 取用,因此有特別寫加強版的 UIHint 來傳遞一些 View 使用的參數,在 ASP.NET Core 上面和以前作法會有點不同,特別記錄一下作法。

前言

.NET Framework 時候可以透過實做 IMetadataAware 介面,將參數放入 ModelMetadata.AdditionalValues 然後在 View 取出資料來傳遞需要的參數,但是在 .NET Core 的世界沒有這一個介面可以實做了,因此改用 IDisplayMetadataProvider Interface 來達到一樣的效果,底下說明實做和使用方式。

實做

實做 UIHintPlusProvider

首先繼承 IDisplayMetadataProvider 並且實做 CreateDisplayMetadata 方法,在方法裡面針對 UIHintAttribute 取出 ControlParameters 參數加入到 AdditionalValues 內。

/// <summary>
/// 加強 UIHint 功能
/// </summary>
public class UIHintPlusProvider : IDisplayMetadataProvider
{
    public UIHintPlusProvider() { }

    public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
    {
        if (context.PropertyAttributes != null)
        {
            foreach (object propAttr in context.PropertyAttributes)
            {
                UIHintAttribute uiHintAttribute = propAttr as UIHintAttribute;
                if (uiHintAttribute != null && uiHintAttribute.ControlParameters != null)
                {
                    foreach (var item in uiHintAttribute.ControlParameters)
                    {
                        context.DisplayMetadata.AdditionalValues.Add(item.Key, item.Value);
                    }
                }
            }
        }
    }
}

在使用上只要在 Startup 內的 ConfigureServices 加入 UIHintPlusProvider 即可。

services.AddMvc((options) => {
    options.ModelMetadataDetailsProviders.Add(new UIHintPlusProvider());
});

實做 HtmlHelper 方便取用 AdditionalValues

接下來為了方便在 View 方便存取 AdditionalValues,實做 HtmlHelper 來方便存取和轉型參數。

HtmlHelperExtension

/// <summary>
/// HtmlHelper Extension
/// </summary>
public static class HtmlHelperExtension
{
    /// <summary>
    /// 用指定的 key 去抓 AdditionalValues 中的Value
    /// </summary>
    public static string GetAdditionalValue(this IHtmlHelper helper, string key)
    {
        return GetAdditionalValue<string>(helper, key, default);
    }
    /// <summary>
    /// 用指定的 key 去抓 AdditionalValues 中的Value
    /// </summary>
    public static T GetAdditionalValue<T>(this IHtmlHelper helper, string key)
    {
        return GetAdditionalValue<T>(helper, key, default);
    }
    /// <summary>
    /// 用指定的 key 去抓 AdditionalValues 中的Value
    /// 並在沒有設定時傳回預設值
    /// </summary>
    public static T GetAdditionalValue<T>(this IHtmlHelper helper, string key, T defaultValue)
    {
        var dic = helper.ViewContext.ViewData.ModelMetadata.AdditionalValues;
        if (dic != null && dic.ContainsKey(key))
        {
            var value = dic[key].ToConvertOrDefault<T>(defaultValue);
            return value;
        }
        return defaultValue;
    }

ObjectExtension

/// <summary>
/// Object Extension
/// </summary>
public static class ObjectExtension
{
    /// <summary>
    /// 轉型或取得預設值
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="obj"></param>
    /// <param name="defaultValue"></param>
    /// <returns></returns>
    public static T ToConvertOrDefault<T>(this object obj, T defaultValue)
    {
        try
        {
            return (T)Convert.ChangeType(obj, typeof(T));
        }
        catch
        {
            return defaultValue;
        }
    }
}

到這邊主要程式就都完成了,接下來就是如何使用的部分。

使用方式

建立一個 LoginViewModel 並且加上 UIHint Attrubute

這邊我在 UIHint 加上了額外的參數 PlaceHolderText 並且設定成帳號和密碼,如果需要多組參數只要再往後加入成對的參數即可。

public class LoginViewModel
{
    /// <summary>
    /// Account
    /// </summary>
    [UIHint("PlaceHolder", "MVC", "PlaceHolderText", "帳號")]
    public string Account { get; set; }
    /// <summary>
    /// Password
    /// </summary>
    [UIHint("PlaceHolder", "MVC", "PlaceHolderText", "密碼")]
    public string Password { get; set; }
}

新增 EditorTemplates

這邊針對編輯畫面做展示,在 Views\Shared\EditorTemplates 新增一個 PlaceHolder.cshtml ,透過 HtmlHelper 取出前面設定 PlaceHolderText 值,放入到 Input 的 placeholder 屬性內。

@model string
@{ 
    var placeHolderText = Html.GetAdditionalValue("PlaceHolderText");
    var nameForModel = Html.NameForModel();
}
<input id="@nameForModel" name="@nameForModel" type="text" placeholder="@placeHolderText" class="control-label" />

新增 Controller 和 View

這邊新增一個 AccountController 和 Index Action

public class AccountController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}
<div class="row">
    <div class="col-md-4">
        <form asp-action="Index">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Account" class="control-label"></label>
                @Html.EditorFor(m=>m.Account)
                <span asp-validation-for="Account" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Password" class="control-label"></label>
                @Html.EditorFor(m => m.Password)
                <span asp-validation-for="Password" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

執行結果

執行之後就可以看到呈現的畫面和原始碼如是有取到我們設定的 PlaceHolder 的值。

以上更詳細的程式碼可以參考 GitHub,裡面有提供完整的程式範例

結論

ASP.NET Core 可以使用 Tag Helper 來更方便呈現 HTML,但是還是沒有辦法完全取代 HtmlHelper,實務上我習慣會搭配 UIHint 跟 Template 來達到更大得客制化,相對實做 Tag Helper 也來的簡單跟方便修改。

參考資料

  1. IMetadataAware 介面
  2. ModelMetadata.AdditionalValues
  3. IDisplayMetadataProvider Interface