緊接着上面的內容,我們繼續看下動態模型頁面交互實現方式,內容如下:
1,如何實現動態表單
2,如何接收表單數據並綁定到動態模型上
一、如何實現動態表單
由於模型信息都是后台自定義配置的,並不是固定不變的結構,所以沒有辦法直接在頁面上寫出對應的表單數據,而需要通過解析模型的結構,動態的生成對應的表單。在說具體實現方法前,我們先來看下我們想要達到的效果。
Html.Raw(FormGenerator.Generate(Model,Properties))
FormGenerator.Generate包含兩個參數,一個動態模型對象,一個需要呈現的屬性列表,方法返回最終生成的form表單html,然后通過Html.Raw呈現到頁面上。
下面介紹一下實現過程,首先定義一個IDynamicFormGenerator接口,代碼如下:
public interface IDynamicFormGenerator { string Generate(object obj, IEnumerable<RuntimeModelMeta.ModelPropertyMeta> properties); }
接口中只包含一個方法,就是我們上面實例代碼用到的方法。接口實現邏輯上很簡單,只需要循環每一個屬性,根據屬性的特點生成一個表單,並把對象數據綁定到表單上,比如屬性如果是bool類型,我們可以生成一個checkbox,然后根據obj對應的屬性值是true還是false,進而設置checkbox的選中狀態。具體實現代碼:
public class DynamicFormGenerator : IDynamicFormGenerator { public string Generate(object obj, IEnumerable<RuntimeModelMeta.ModelPropertyMeta> properties) { StringBuilder builder = new StringBuilder(); //循環屬性集合 foreach (var item in properties) { //TODO:根據屬性信息生成表單並綁定數據 string fieldhtml=""; //把生成的html加入到stringbulder中 builder.Append(fieldhtml); } //返回最終的html結果 return builder.ToString(); } }
這個方法里最主要的部分就是如何根據屬性的類型生成對應的表單,就是如何得到上面的fieldhtml?我們再來看下前面定義的RuntimeModelMeta
public class RuntimeModelMeta { public int ModelId { get; set; } public string ModelName { get; set; }//模型名稱 public string ClassName { get; set; }//類名稱 public string Properties{get;set;}//屬性集合json序列化結果 public class ModelPropertyMeta { public string Name { get; set; }//對應的中文名稱 public string PropertyName { get; set; } //類屬性名稱 public int Length { get; set; }//數據長度,主要用於string類型 public bool IsRequired { get; set; }//是否必須輸入,用於數據驗證 public string ValueType { get; set; }//數據類型,可以是字符串,日期,bool等 } }
ModelPropertyMeta里包含了一個ValueType信息,就是當前屬性的數據類型,那我們是否可以根據這個來確定生成的表單形式?答案是否定的,因為即使是同一種類型也會呈現不同的表單,比如都是字符串,可能有的要求呈現下拉框,有的要求呈現復選框,所以只依靠ValueType還不夠,我們可以給RuntimeModelMeta增加一個屬性,專門用於設置表單形式的,改造后代碼如下:
public class RuntimeModelMeta { public int ModelId { get; set; } public string ModelName { get; set; }//模型名稱 public string ClassName { get; set; }//類名稱 public string Properties{get;set;}//屬性集合json序列化結果 public class ModelPropertyMeta { public string Name { get; set; }//對應的中文名稱 public string PropertyName { get; set; } //類屬性名稱 public int Length { get; set; }//數據長度,主要用於string類型 public bool IsRequired { get; set; }//是否必須輸入,用於數據驗證 public string ValueType { get; set; }//數據類型,可以是字符串,日期,bool等 public string ShowType { get; set; }//表單形式 } }
有了ShowType,我們就可以根據設置的類型來生成對應的表單。首先先定義個表單生成器接口,代碼如下:
public interface IDynamicFormFieldGenerator { //根據傳遞的屬性及對象,生成表單 string Generate(object obj, RuntimeModelMeta.ModelPropertyMeta meta,bool onlyform=false); //這個表示當前的實現是針對哪一種ShowType的 string ForType { get; } }
然后針對每一種ShowType實現一個生成器,比如針對checkbox類型的ShowType,我們實現一個生成器,代碼如下:
public class CheckboxFieldGenerator : IDynamicFormFieldGenerator { public string ForType { get { return "checkbox"; } } public string Generate(object obj, RuntimeModelMeta.ModelPropertyMeta meta,bool onlyform=false) { //把動態對象轉換成一個DynamicEntity,為的是后面獲取數據方便,因為DynamicEntity支持通過索引獲取屬性數據 DynamicEntity entity = obj as DynamicEntity; if (obj == null) { throw new NullReferenceException("DynamicEntity"); } //通過entity[meta.PropertyName]獲取到屬性數據 return string.Format("<input id='{1}' name='{1}' type='checkbox' value='{0}'/>", entity[meta.PropertyName]?.ToString(), meta.PropertyName); } }
其他ShowType類型,可以根據自己系統的需要,直接實現即可,這里不再一一列舉了。
有了表單構造器,我們再回頭完善下DynamicFormGenerator,在DynamicFormGenerator中,我們需要根據屬性的ShowType信息獲取到IDynamicFormFieldGenerator,我們可以定義一個DynamicFormFieldGeneratorProvider,方便我們得到我們所需要的IDynamicFormFieldGenerator,具體實現代碼:
//提供者接口定義 public interface IDynamicFormFieldGeneratorProvider { IDynamicFormFieldGenerator Get(string type); }
public class DynamicFormFieldGeneratorProvider: IDynamicFormFieldGeneratorProvider { private readonly IHttpContextAccessor _httpContextAccessor; private IEnumerable<IDynamicFormFieldGenerator> _generators; public DynamicFormFieldGeneratorProvider(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public IDynamicFormFieldGenerator Get(string type) { //通過依賴注入,獲取到構造器實現集合,所以我們需要把所有的構造器實現都注冊到ServiceCollection中 if (_generators==null) { _generators= _httpContextAccessor.HttpContext.RequestServices.GetServices<IDynamicFormFieldGenerator>(); } if (_generators==null) { throw new NotSupportedException("IDynamicFormFieldGenerator"); } //根據type找到第一個符合條件的構造器,並返回 IDynamicFormFieldGenerator g = _generators.FirstOrDefault(m => m.ForType == type); if (g==null) { throw new NotSupportedException("not supproted for " + type + "'s form field generator"); } return g; } }
條件都准備好了,直接完善DynamicFormGenerator,最終代碼如下:
public class DynamicFormGenerator : IDynamicFormGenerator { private readonly IDynamicFormFieldGeneratorProvider _fieldGeneratorProvider; public DynamicFormGenerator(IDynamicFormFieldGeneratorProvider provider) { _fieldGeneratorProvider = provider; } public string Generate(object obj, IEnumerable<RuntimeModelMeta.ModelPropertyMeta> properties) { StringBuilder builder = new StringBuilder(); foreach (var item in properties) { //根據showtype獲取表單構造器 IDynamicFormFieldGenerator fieldGenerator = _fieldGeneratorProvider.Get(item.ShowType); builder.Append(fieldGenerator.Generate(obj,item)); } return builder.ToString(); } }
到此表單就可以呈現到界面上了。
二、如何接收表單數據並綁定到動態模型上
在mvc中提供了數據綁定機制,可以快速的把表單數據綁定到對象上,但是現在我們的對象是動態的,那又該如何應對?
在表單操作中,當前對應的模型我們肯定知道,所以可以借助前面介紹的內容,我們先得到一個動態模型對象,具體操作如下:
//根據模型id獲取到type Type modelType = _runtimeModelProvider.GetType(modelid); //實例化 object obj = Activator.CreateInstance(modelType);
我們現在需要解決的是,如何把動態表單提交的數據綁定到obj的屬性上。方法也很簡單,在mvc中給我們提供了很好的支持,方法就是TryUpdateModelAsync,借助這個方法,就可以很方便的把數據綁定到obj上,具體調用實例
TryUpdateModelAsync(obj, modelType, "")
obj就是上面實例化的對象,modelType就是動態模型對應的Type信息,有了數據后,后面就是通過ef完成數據庫同步的事了,比如增加
ShopDbContext.Add(obj); ShopDbContext.SaveChanges();
所有代碼還是需要大家自己完善補充,如果有不足之處,歡迎大家能夠批評指正。