设计指导 - 数据库中松散类型键/值的动态ViewModel

时间:2013-12-31 22:34:35

标签: c# asp.net-mvc nhibernate razor architecture

我有一个令我头痛的场景,我认为我正朝着正确的方向前进,现在我发现了另一个问题。这很复杂,让我们从数据库开始:

我有一个带有键的表,一个值,一个typeId和一些其他属性。我所关心的只是关键和价值。我的viewmodel包含我从nhibernate在存储库层中使用的数据对象映射的域对象的列表。这是:

public class DomainModel
{
    public string Key { get; set; }
    public string Value { get; set; }
    public TypeEnum Type { get; set; }
}

**注意,TypeEnum与Key的值的强类型无关。这只是对键/值进行分类的一种不同方式,以便我可以按类型从数据库中提取它们。

简单。这是我的viewmodel:

public class ViewModel
{
    public List<DomainModel> Models { get; set; }
}

我遇到的问题是密钥的值是不同的数据类型。有时它们是布尔值,我想要一个Html.CheckBoxFor(model =&gt; model.Value),有时它们是字符串,而Html.TextBoxFor(model =&gt; model.Value)就足够了。

这是我的剃刀观点:

@foreach (var setting in Model.Models)
{
    <tr>
        <td>@Html.Label(setting.Key)</td>
        <td>@Html.TextBox(setting.Value)</td>
    </tr>
}

应用程序在此处切片的最佳区域是什么,并进行某些类型检查或某些事情,以便我在页面上有适当的Html元素?我怎么会这样做呢?我错过了一些非常明显和简单的东西吗?此外,如何根据Key的值获取Keys的显示名称属性?他们目前只是PacalCasedBunchedDescriptionNames。我在这里设计还是什么呢?

2 个答案:

答案 0 :(得分:1)

我最终做的只是让视图和视图模型保持平坦,而不是试图在那里做一些动态类型的暗示。这也使我能够保持我的验证和其他属性的整洁。

Domain Model仍然是相同的,我实际上最终删除了Type,并且在我的Business Layer中创建了一个SaveDomainModel,因为每次我保存/更新一个集合时,它只适用于一种类型的设置:

public class SaveDomainModel
{
    public List<DomainModel> DomainModels { get; set; }
    public SettingTypeEnum SettingType { get; set; }
}

我将DomainModel更改为:

public class DomainModel
{
    [Required]
    public string Key { get; set; }
    [Required]
    public string Value { 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; }
}

所以现在我的控制器看起来像是POST:

[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);
}

这是我在之前没有遇到的缺失链接,我在这篇文章中偶然发现:Enumerating through an object's properties (string) in C#

然后从平面视图模型映射到Domain obj我在SaveSettings()中使用了Map ...函数。

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>
...>

然后为了完成它,我在我的Repository中添加了一个UpdateCollection(),在从DomainObj映射后,在服务层中调用了它 - &gt; 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;
}

答案 1 :(得分:0)

一种方法是使用MVC功能来实现它,而不是触及nhibernate细节。我的意思是,nhibernate只是你的数据层吗?这根本不会影响您的表示层。

您的模型上已经有TypeEnum属性。 我想这将定义属性是否应显示为复选框,文本框或其他...如果是,请为您的DomainModel类型编写自定义 editortemplate ,并将逻辑放在一个位置如何呈现DomainModel的实例。

如果您对MVC中的编辑器模板感到好奇,请查看Scott's blogthis one

为您举例说明这种情况:

模特:

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
}

用于测试的控制器/动作:

    public ActionResult Index()
    {
        var entity = new Entity();
        entity.Add(new Property()
        {
            DisplayType = DisplayType.Checkbox,
            Name = "Check1",
            Value = "True"
        });

        entity.Add(new Property()
        {
            DisplayType = DisplayType.Checkbox,
            Name = "Check2",
            Value = "False"
        });

        entity.Add(new Property()
        {
            DisplayType = DisplayType.TextBox,
            Name = "Input1",
            Value = ""
        });

        //ViewBag.Entity = entity;
        return View(entity);
    }

View可能如下所示:

@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>
}

所有魔法现在隐藏在@Html.EditorFor(m => m[i], "PropertyEditor")内 在“查看/共享”下创建文件夹EditorTemplates,然后为您的模板添加文件,例如PropertyEditor.cshtml

模板可能如下所示:

@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;
    }
}