C# 设计指南-数据库中松散类型键/值的动态视图模型
我有一个让我头疼的场景,我认为我走的方向是正确的,现在我发现了另一个问题。这很复杂,让我们从数据库开始: 我有一个表,表中有一个键、一个值、一个typeId和一些其他属性。我关心的只是关键和价值。我的viewmodel包含我从nhibernate在存储库层中使用的数据对象映射的域对象列表。这是:C# 设计指南-数据库中松散类型键/值的动态视图模型,c#,asp.net-mvc,nhibernate,razor,architecture,C#,Asp.net Mvc,Nhibernate,Razor,Architecture,我有一个让我头疼的场景,我认为我走的方向是正确的,现在我发现了另一个问题。这很复杂,让我们从数据库开始: 我有一个表,表中有一个键、一个值、一个typeId和一些其他属性。我关心的只是关键和价值。我的viewmodel包含我从nhibernate在存储库层中使用的数据对象映射的域对象列表。这是: public class DomainModel { public string Key { get; set; } public string Value { get; set; }
public class DomainModel
{
public string Key { get; set; }
public string Value { get; set; }
public TypeEnum Type { get; set; }
}
**注意,TypeEnum与键的值的强类型无关。这只是对键/值进行分类的另一种方式,这样我就可以按该类型从数据库中提取它们
简单。这是我的viewmodel:
public class ViewModel
{
public List<DomainModel> Models { get; set; }
}
公共类视图模型
{
公共列表模型{get;set;}
}
我的问题是键的值是不同的数据类型。有时它们是布尔值,我想要一个Html.CheckBoxFor(model=>model.Value),有时它们是字符串,一个Html.TextBoxFor(model=>model.Value)就足够了
以下是我的观点:
@foreach (var setting in Model.Models)
{
<tr>
<td>@Html.Label(setting.Key)</td>
<td>@Html.TextBox(setting.Value)</td>
</tr>
}
@foreach(Model.Models中的变量设置)
{
@Label(setting.Key)
@Html.TextBox(setting.Value)
}
什么是应用程序的最佳区域,可以在这里进行切片,并进行一些类型检查或其他操作,以便在页面上显示适当的Html元素?我该怎么做呢?我是否错过了一些非常明显和简单的东西?此外,如何根据键的值获取键的“显示名称”属性?目前它们只是pacalCasedBunchedDescriptionName。我是不是在设计上有点偏离了呢?一种方法是只使用MVC特性来实现这一点,而不涉及nhibernate的细节。我是说,nhibernate只是你的数据层,对吗?这根本不会影响您的表示层 您的模型上已经有了
TypeEnum
属性。
我猜这将定义属性是否应显示为复选框、文本框或其他任何形式。。。如果是这样,请为您的DomainModel
类型编写一个自定义编辑器或模板,并将如何显示DomainModel
实例的逻辑放在一个位置
如果您对MVC中的编辑器模板感到好奇,请查看或
给你一个这样的例子:
模型:
public class Entity : List<Property>
{
public Entity()
{
}
}
public class Property
{
public string Name { get; set; }
public string Value { get; set; }
public DisplayType DisplayType { get; set; }
}
public enum DisplayType
{
TextBox,
Checkbox
}
视图可能如下所示:
@using WebApplication6.Models
@model WebApplication6.Models.Entity
@{
ViewBag.Title = "Edit Entity";
}
<h2>Edit Entity</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Entity</h4>
<hr />
@Html.ValidationSummary(true)
<div class="form-group">
@for (var i = 0; i < Model.Count;i++ )
{
<div class="form-group">
@Html.EditorFor(m => m[i], "PropertyEditor")
</div>
}
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
@model WebApplication6.Models.Property
@if (Model != null)
{
<label for="@Model.Name" class="col-sm-2 control-label">@Model.Name</label>
switch (Model.DisplayType)
{
case WebApplication6.Models.DisplayType.TextBox:
<div class="col-sm-10">@Html.TextBox(Model.Name, Model.Value)</div>
break;
case WebApplication6.Models.DisplayType.Checkbox:
<div class="col-sm-10">@Html.CheckBox(Model.Name, bool.Parse(Model.Value))</div>
break;
}
}
我最后做的只是将视图和viewmodel保持为平面,而不是尝试在那里进行一些动态类型暗示。这也允许我保持我的验证和其他属性的整洁 域模型仍然是一样的,实际上我最终删除了类型,并在我的业务层中创建了一个SaveDomainModel,因为每次我保存/更新集合时,它仅适用于1种类型的设置:
public class SaveDomainModel
{
public List<DomainModel> DomainModels { get; set; }
public SettingTypeEnum SettingType { get; set; }
}
并展平了我的ViewModel:
public class EditViewModel
{
[DisplayName("Display Name:")]
[Required]
public int AnIntProp { get; set; }
[DisplayName("Another Display Name:")]
[Required]
public string HereIsAString { get; set; }
[DisplayName("Bool Display:")]
[Required]
public bool ImABool{ get; set; }
}
现在我的控制器在帖子中是这样的:
[HttpPost]
public virtual ActionResult Edit(EditViewModel viewModel)
{
if (ModelState.IsValid)
{
SaveSettings(viewModel);
return RedirectToAction(MVC.Settings.Edit());
}
return View(viewModel);
}
private void SaveSettings(EditViewModel viewModel)
{
var settings = MapEditViewModelToDomainModels(viewModel);
var saveDomainModel = new SaveDomainModel
{
DomainModels = settings,
SettingType = SettingTypeEnum.Application
};
_settingsService.SaveSettings(saveDomainModel);
}
这是我在这篇文章中偶然发现的缺失环节:
然后,为了从平面视图模型映射到域对象,我使用了该映射。。。函数在SaveSettings()中
私有静态列表映射EditViewModel到域模型(EditViewModel viewModel)
{
var设置=新列表();
var stringPropertyNamesAndValues=viewModel.GetType().GetProperties().Where(p=>p.CanRead)。选择(p=>new{Name=p.Name,Value=p.GetValue(viewModel,null)});
foreach(stringPropertyNamesAndValues中的变量对)
{
var模型=新域模型
{
Key=pair.Name,
Value=pair.Value.ToString()
};
设置。添加(模型);
}
返回设置;
}
然后我就可以像这样保持视野平坦:
<tr>
<td>@Html.LabelFor(model => model.SomeString)</td>
<td>@Html.TextBoxFor(model => model.SomeString)</td>
</tr>
<tr>
<td>@Html.LabelFor(model => model.SomeBoolean)</td>
<td>@Html.CheckBoxFor(model => model.SomeBoolean)</td>
</tr>
...>
@LabelFor(model=>model.SomeString)
@Html.TextBoxFor(model=>model.SomeString)
@LabelFor(model=>model.SomeBoolean)
@CheckBoxFor(model=>model.SomeBoolean)
...>
然后为了完成它,我在我的存储库中添加了一个UpdateCollection(),它显然是在从DomainObj->DataObj映射之后在服务层中调用的
public void SaveSettings(SaveDomainModel model)
{
var settings = MapDomainModelToList(model).AsQueryable();
_repository.UpdateCollection(settings);
}
private IEnumerable<DataObj> MapDomainModelToList(SaveDomainModel saveDomainModel)
{
var settings = new List<Setting>();
foreach (var domainModel in saveDomainModel.DomainModels)
{
var setting = GetSetting(domainModel.Key, saveDomainModel.SettingType);
if (!String.Equals(setting.Value, domainModel.Value, StringComparison.CurrentCultureIgnoreCase))
{
setting.Value = domainModel.Value;
setting.LastUpdated = DateTime.Now;
settings.Add(setting);
}
}
return settings;
}
public bool UpdateCollection(IQueryable<T> entities)
{
using (var transaction = _session.BeginTransaction())
{
foreach (var entity in entities)
{
_session.Update(entity);
}
transaction.Commit();
}
return true;
}
public void保存设置(SaveDomainModel模型)
{
var settings=MapDomainModelToList(model).AsQueryable();
_repository.UpdateCollection(设置);
}
私有IEnumerable MapDomainModelToList(SaveDomainModel SaveDomainModel)
{
var设置=新列表();
foreach(saveDomainModel.DomainModels中的var domainModel)
{
var setting=GetSetting(domainModel.Key,saveDomainModel.SettingType);
如果(!String.Equals(setting.Value、domainModel.Value、StringComparison.CurrentCultureIgnoreCase))
{
setting.Value=domainModel.Value;
setting.LastUpdated=DateTime.Now;
设置。添加(设置);
}
}
返回设置;
}
公共bool UpdateCollection(IQueryable实体)
{
使用(var事务=_session.BeginTransaction())
{
foreach(实体中的var实体)
{
_更新(实体);
}
Commit();
}
返回true;
}
您需要一些元数据。这些应该由TypeEnum
开关驱动。您可以/应该在任何地方创建一些可用的提供程序模式实现,提供对这些元数据的只读访问。有了这些,我们可以使用元数据(例如BL上的元数据)进行验证,MVC视图可以基于这些设置驱动渲染。。。(这里有一个关于NHibernate dynamic world的小概述)感谢您花时间回复,但是我应该在我的原始帖子中澄清,TypeEnum与键的值的强.net类型无关。它只是一个枚举,允许我从数据库中提取特定类型的键/值,因为它们用于应用程序的各个领域。但这是一个想法。向数据库添加另一个int
private static List<DomainModel> MapEditViewModelToDomainModels(EditViewModel viewModel)
{
var settings = new List<DomainModel>();
var stringPropertyNamesAndValues = viewModel.GetType().GetProperties().Where(p => p.CanRead).Select(p => new {Name = p.Name, Value = p.GetValue(viewModel, null)});
foreach (var pair in stringPropertyNamesAndValues)
{
var model= new DomainModel
{
Key = pair.Name,
Value = pair.Value.ToString()
};
settings.Add(model);
}
return settings;
}
<tr>
<td>@Html.LabelFor(model => model.SomeString)</td>
<td>@Html.TextBoxFor(model => model.SomeString)</td>
</tr>
<tr>
<td>@Html.LabelFor(model => model.SomeBoolean)</td>
<td>@Html.CheckBoxFor(model => model.SomeBoolean)</td>
</tr>
...>
public void SaveSettings(SaveDomainModel model)
{
var settings = MapDomainModelToList(model).AsQueryable();
_repository.UpdateCollection(settings);
}
private IEnumerable<DataObj> MapDomainModelToList(SaveDomainModel saveDomainModel)
{
var settings = new List<Setting>();
foreach (var domainModel in saveDomainModel.DomainModels)
{
var setting = GetSetting(domainModel.Key, saveDomainModel.SettingType);
if (!String.Equals(setting.Value, domainModel.Value, StringComparison.CurrentCultureIgnoreCase))
{
setting.Value = domainModel.Value;
setting.LastUpdated = DateTime.Now;
settings.Add(setting);
}
}
return settings;
}
public bool UpdateCollection(IQueryable<T> entities)
{
using (var transaction = _session.BeginTransaction())
{
foreach (var entity in entities)
{
_session.Update(entity);
}
transaction.Commit();
}
return true;
}