使用ViewModel

时间:2015-12-07 17:41:30

标签: c# asp.net-mvc asp.net-mvc-4 razor

我已经全身心投入,并且互联网正在寻找解决方案,虽然我发现了一些让我接近的帖子,但他们最终都失败了#34;对象参考未设置为对象的实例"错误。

我正在尝试在MVC中创建"嵌套集合模型的变体以添加多个电话号码"来自Itorian.com,但我的复杂程度更高。

基本上,我有一个MVC 5 EF 6应用程序,其设置允许无限数量的电话号码与任何给定的联系人相关联。我使用链接表将联系人ID与电话ID相关联。目的是允许多个联系人(即,家庭成员)共享单个电话号码,同时还允许一个联系人具有多个电话号码。从第三个表格设置Phonetype(家庭,工作,小区等),允许下拉选择。 phonelink表包含phonetype_id,phone_id和contact_id的列。

我还应该注意,客户扩展了我的基础联系人。电话号码与联系人相关联,但我实际上是在创建客户端以同时生成扩展字段。如果我不在电话中输入电话,这种方法非常有效。

我的问题有两个:

首先: 我需要找出导致DropDownListFor

上的Object Reference Not Set错误的原因

第二 确定在单击“添加电话号码”链接时如何正确地将新字段添加到视图中。关于此的更多细节。

我的模型类,由EF生成的数据库:

Phone.cs

public partial class phone
{
    public phone()
    {
        this.phonelinks = new HashSet<phonelink>();
    }

    public int id { get; set; }
    public string phone_number { get; set; }
    public string phone_extension { get; set; }

    public virtual ICollection<phonelink> phonelinks { get; set; }
}

Phonetype.cs

public partial class phonetype
{
    public phonetype()
    {
        this.phonelinks = new HashSet<phonelink>();
    }

    public int id { get; set; }
    public string phone_type { get; set; }

    public virtual ICollection<phonelink> phonelinks { get; set; }
}

Phonelink.cs

public partial class phonelink
{
    public int id { get; set; }
    public int contact_id { get; set; }
    public int phone_id { get; set; }
    public int phonetype_id { get; set; }

    public virtual contact contact { get; set; }
    public virtual phone phone { get; set; }
    public virtual phonetype phonetype { get; set; }
}

我还扩展了phonelink以包含一个Remove From View帮助器属性(通过HTML Helpers和JS按预期工作):

public partial class phonelink
{
    public bool remove_from_view { get; set; }
}

我的控制器用于新的联系人(只是GET,因为我的错误发生在尝试POST之前)

ClientsController.cs

// GET: Clients/Create
public ActionResult Create()
{
    var viewModel = new ClientCreateViewModel();
    ConfigureCreateViewModel(viewModel);

    return View(viewModel);
}

private void ConfigureCreateViewModel(ClientCreateViewModel model)
{
    model.AllEthnicities = new SelectList(db.ethnicities, "id", "ethnicity1");
    model.AllGenders = new SelectList(db.genders, "id", "gender1");
    model.AllPrefixes = new SelectList(db.prefixes, "id", "prefix1");
    model.AllSuffixes = new SelectList(db.suffixes, "id", "suffix1");
    model.PhoneLinkVM = new PhoneLinkViewModel()
    {
        AllPhoneTypes = new SelectList(db.phonetypes, "id", "phone_type")
    };
}

我的ViewModel(简化)

ClientViewModel.cs

public class ClientCreateViewModel
{
    public client Clients { get; set; }
    public PhoneLinkViewModel PhoneLinkVM { get; set; }

    // There are other declarations here for Ethnicities, Gender, etc.
    // They all work for displaying dropdowns, so have been removed for simplicity
}

public class PhoneLinkViewModel
{
    public IEnumerable<phonelink> Phonelinks { get; set; }
    public IEnumerable<SelectListItem> AllPhoneTypes { get; set; }
    public int SelectedPhoneType { get; set; }
    public phonelink phonelink { get; set; }
}

在下面的视图中,我从编辑器模板中调用了phonelink。

Create.cshtml

@* Div for Phone Numbers *@
<div id="phoneNumbers">
    @Html.EditorFor(model => model.PhoneLinkVM.phonelink)
</div>
<div class="row top-space">
    <div class="form-group col-md-4 col-xs-12">
        @Html.AddLink("Add Phone Number", "#phoneNumbers", ".phoneNumber", "Phonelinks", typeof(CreateClientViewModel))
    </div>
</div>

Phonelink.cshtml

@model TheraLogic.Models.ClientCreateViewModel

@using TheraLogic.Models

<div class="phoneNumber">
    <div class="row top-space">
        @* Phone Number Inputs *@
        <div class="form-group col-md-2">
            <label class="control-label">Phone Number</label>
            @Html.EditorFor(model => model.PhoneLinkVM.phonelink.phone.phone_number, new { htmlAttributes = new { @class = "form-control" } })
            @Html.HiddenFor(model => model.PhoneLinkVM.phonelink.remove_from_view, new { @class = "mark-for-removal" })
            @Html.RemoveLink("Remove", "div.phoneNumber", "input.mark-for-removal")
        </div>
        <div class="form-group col-md-1">
            <label class="control-label">Extension</label>
            @Html.EditorFor(model => model.PhoneLinkVM.phonelink.phone.phone_extension, new { htmlAttributes = new { @class = "form-control" } })
        </div>
        <div class="form-group col-md-1">
            <label class="control-label">Type</label>
            @Html.DropDownListFor(model => model.PhoneLinkVM.SelectedPhoneType, Model.PhoneLinkVM.AllPhoneTypes, htmlAttributes: new { @class = "form-control" })
        </div>
    </div>
</div>

如果我在编辑器模板的底部注释掉@ Html.DropDownListFor,我可以查看Create view而不会出错。但有了它,就会产生Object Reference Not Set错误。

我能够使其完全正常工作的唯一方法是添加:

ViewBag.ThesePhoneTypes = model.PhoneLinkVM.AllPhoneTypes;

到ConfigureCreateViewModel的底部,并使用

将其拉入编辑器模板中的视图
@Html.DropDownList("ThesePhoneTypes")

这更像是一种解决方法而非真正的解决方案。

关于在点击链接时添加/删除电话号码,如果我在Create.cshtml的@ Html.AddLink助手中使用type(phonelink),则会收到错误消息:

  

传递到字典中的模型项是类型的   &#39; phonelink&#39;,但这个字典需要一个模型项   键入&#39; Models.ClientCreateViewModel&#39;

但是如果我使用type(ClientCreateViewModel然后而不是在我的Phonelink.cshtml编辑器模板中获取内容,我只是获取一组输入,用于在ClientCreateViewModel中为SelectedGender,SelectedEthnicity等声明的整数。上面的ViewModel代码,因为它们工作)。我知道我需要深入研究绑定嵌套的PhoneLinkViewModel,但我不确定如何去做。

HtmlHelpers.cs

(几乎逐字逐句地从上面的Itorian.com链接中获取)

public static class HtmlHelpers
{
    public static IHtmlString RemoveLink(this HtmlHelper htmlHelper, string linkText, string container, string deleteElement)
    {
        var js = string.Format("javascript:removeNestedForm(this,'{0}','{1}'); return false;", container, deleteElement);
        TagBuilder tb = new TagBuilder("a");
        tb.Attributes.Add("href", "#");
        tb.Attributes.Add("onclick", js);
        tb.InnerHtml = linkText;
        var tag = tb.ToString(TagRenderMode.Normal);
        return MvcHtmlString.Create(tag);
    }

    public static IHtmlString AddLink<TModel>(this HtmlHelper<TModel> htmlHelper, string linkText, string container, string counter, string collectionProperty, Type nestedType)
    {
        var ticks = DateTime.UtcNow.Ticks;
        var nestedObject = Activator.CreateInstance(nestedType);
        var partial = htmlHelper.EditorFor(x => nestedObject).ToHtmlString().JsEncode();
        partial = partial.Replace("id=\\\"nestedObject", "id=\\\"" + collectionProperty + "_" + ticks + "_");
        partial = partial.Replace("name=\\\"nestedObject", "name=\\\"" + collectionProperty + "[" + ticks + "]");
        var js = string.Format("javascript:addNestedForm('{0}','{1}','{2}','{3}');return false;", container, counter, ticks, partial);
        TagBuilder tb = new TagBuilder("a");
        tb.Attributes.Add("href", "#");
        tb.Attributes.Add("onclick", js);
        tb.InnerHtml = linkText;
        var tag = tb.ToString(TagRenderMode.Normal);
        return MvcHtmlString.Create(tag);
    }

    private static string JsEncode(this string s)
    {
        if (string.IsNullOrEmpty(s)) return "";
        int i;
        int len = s.Length;
        StringBuilder sb = new StringBuilder(len + 4);
        string t;
        for (i = 0; i < len; i += 1)
        {
            char c = s[i];
            switch (c)
            {
                case '>':
                case '"':
                case '\\':
                    sb.Append('\\');
                    sb.Append(c);
                    break;
                case '\b':
                    sb.Append("\\b");
                    break;
                case '\t':
                    sb.Append("\\t");
                    break;
                case '\n':
                    break;
                case '\f':
                    sb.Append("\\f");
                    break;
                case '\r':
                    break;
                default:
                    if (c < ' ')
                    {
                        string tmp = new string(c, 1);
                        t = "000" + int.Parse(tmp, System.Globalization.NumberStyles.HexNumber);
                        sb.Append("\\u" + t.Substring(t.Length - 4));
                    }
                    else
                    {
                        sb.Append(c);
                    }
                    break;
            }
        }
        return sb.ToString();
    }
}

我也可以添加Javascript,如果需要,但我认为没关系,因为它确实为视图添加了带字段的DIV,而不是我需要它的类型(phonelink)。

2 个答案:

答案 0 :(得分:0)

您需要初始化PhoneLinkVM。这不会导致调用其他HtmlHelper时出现问题,因为实际上没有计算作为第一个参数传递的表达式。但是,传递给DropDownListFor的选择列表参数是一个直接变量引用,默认情况下PhoneLinkVM为空。

要修复它,您只需要在视图模型中添加如下构造函数:

public class ClientCreateViewModel
{
    public ClientCreateViewModel()
    {
        PhoneLinkVM = new PhoneLinkViewModel();
    }

    ...
}

答案 1 :(得分:0)

虽然它没有真正回答为什么dropdownlistfor在编辑器模板中失败的问题,但我已经为我的项目找到了解决方案。

我完全抛弃了编辑器模板,现在将PhoneLink div渲染为部分。我刚刚决定使用另一种方法来添加额外的手机链接,这样就无需在我的情况下使用语音链接编辑器模板。

当我将@Html.EditorFor(model => model.PhoneLinkVM.phonelink)更改为@Html.Partial("_PhonelinkPartial", Model.PhoneLinkPartial)时(以及移动/重命名一些相关文件),我的视图按预期呈现。

我将使用JQuery和模态弹出窗口处理“添加电话号码”功能。