包含 属性 个不同模型的 MVC 模型

MVC Model that includes property of a different Model

我对 MVC 比较陌生,我现在的位置是:

我有 2 个模型:

站点模型 -

SiteID() As Integer
Name() As String
Latitude() As Double
Longitude() As Double

以及设备型号 -

DeviceID() As Integer
Make() As String
Model() As String
Serial() As String
Site() As Site

如您所见,设备链接到站点。在数据库上下文中,设备 table 包含外键 "Site_SiteID",如下所示:

我想做的是在创建和编辑的设备视图中,使网站字段成为 DropDownListFor,其列表包含网站 [=56= 中存在的网站].然后将所选站点保存为设备站点。

这是我在“创建”视图中放置的内容,用于创建将出现在下拉列表中的列表...后跟 DropDownListFor:

@ModelType TestWebApplication2.Device
@Code
    ViewData("Title") = "Create"

    Dim selectSite As New List(Of SelectListItem)
    Dim db = New TestWebApplication2.Models.TestWebApplication2Context
    Dim listOfSites As List(Of Site)
    listOfSites = (From site In db.Sites Select site).ToList()

    For Each item In listOfSites
        selectSite.Add(New SelectListItem() With {.Value = item.SiteID, .Text = item.Name})
    Next

End Code

 <div class="form-group">
     @Html.LabelFor(Function(model) model.Site, htmlAttributes:=New With {.class = "control-label col-md-2"})
     <div class="col-md-10">
         @Html.DropDownListFor(Function(model) model.Site, selectSite), New With {.htmlAttributes = New With {.class = "form-control"}})
         @Html.ValidationMessageFor(Function(model) model.Site, "", New With {.class = "text-danger"})
     </div>
 </div>

这是 DevicesController 中的创建 post:

    <HttpPost()>
    <ValidateAntiForgeryToken()>
    Async Function Create(<Bind(Include:="DeviceID,Make,Model,Serial,Site")> ByVal device As Device) As Task(Of ActionResult)
        If ModelState.IsValid Then
            db.Devices.Add(device)
            Await db.SaveChangesAsync()
            Return RedirectToAction("Index")
        End If
        Return View(device)
    End Function

这是因为我可以在“创建”页面的下拉列表中获取站点列表,但是当我点击 "Save" 时,它会给我一个类似 "The value '1' is invalid" 的错误。这是因为它试图传入一个字符串(这是列表项值的类型)而不是站点。

所以我尝试的一件事是将每个下拉列表项的值设置为站点项本身,如下所示:

    selectSite.Add(New SelectListItem() With {.Value = item, .Text = item.Name})

但是它给了我一个错误,它不能将站点转换为字符串,所以你不能传回整个对象。

然后我尝试通过控制器中的 ID 获取它来设置设备的站点,就像这样(注意 "Site" 已从绑定列表中删除):

    <HttpPost()>
    <ValidateAntiForgeryToken()>
    Async Function Create(<Bind(Include:="DeviceID,Make,Model,Serial")> ByVal device As Device) As Task(Of ActionResult)
        If ModelState.IsValid Then
            Dim thisSite As Integer = CInt(Request.Form.Get("Site"))
            device.Site = (From site In db.Sites Where site.SiteID = thisSite Select site).FirstOrDefault()
            db.Devices.Add(device)
            Await db.SaveChangesAsync()
            Return RedirectToAction("Index")
        End If
        Return View(device)
    End Function

这确实可以设置设备的站点,但它实际上并没有保存到数据库中(我假设是因为数据库只想保存外键,而不是实际的站点对象)。所以下次我去喜欢索引或详细信息页面时,设备的站点是 Nothing

我想一定有办法做到这一点,但我不确定从这里还能尝试什么。感谢您的帮助!

将 Site_SiteId 添加到您的设备型号并将下拉列表绑定到该值。

此外,我不会在视图中编写项目列表 - 在控制器中执行此操作并通过 ViewModel (http://sampathloku.blogspot.com/2012/10/how-to-use-viewmodel-with-aspnet-mvc.html) 或 ViewBag 将其传递。

直到现在我还不知道这个叫做 "Navigation Property" 的东西。所以 Site 作为 Device 的 属性 是它自己的模型,将两个表链接在一起。

因此,正如史蒂夫·格林 (Steve Greene) 提示我的那样,我缺少的是在模型中包含 SiteDevice,以便我想在其中使用站点名称 (在索引列表中,并使用谓词进行详细信息和删除)...称为 "Eager Loading".

我还按照建议,使用 ViewData 将生成 DropDownFor 项目列表的代码移到了 Controller。现在如问题中所述,因为那只是传回站点 ID,所以我必须根据他们选择的项目的 ID 手动设置设备的站点。

这就是我的设备控制器现在的样子:

        Async Function Index() As Task(Of ActionResult)
            'Need to have this include Site so that it will pull in that data in order to reference site name in the View
            '(doing this is called Eager Loading)
            Dim devices = db.Devices.Include("Site").ToListAsync()
            Return View(Await devices)
        End Function

        ' GET: Devices/Details/5
        Async Function Details(ByVal id As Integer?) As Task(Of ActionResult)
            If IsNothing(id) Then
                Return New HttpStatusCodeResult(HttpStatusCode.BadRequest)
            End If
            Dim device As Device = Await db.Devices.FindAsync(id)
            If IsNothing(device) Then
                Return HttpNotFound()
            Else
                'Need to have this include Site using Predicate so that it will pull in that data in order to reference site name in the View
                device = db.Devices.Include("Site").Where(Function(x) x.DeviceID = id).FirstOrDefault()
            End If
            Return View(device)
        End Function

        ' GET: Devices/Create
        Function Create()
            'Create the list of items that the DropDownFor in the View will reference
            Dim selectSite As New List(Of SelectListItem)
            Dim db = New TestWebApplication2.Models.TestWebApplication2Context
            Dim listOfSites As List(Of Site)
            listOfSites = (From site In db.Sites Select site).ToList()

            For Each item In listOfSites
                selectSite.Add(New SelectListItem() With {.Value = item.SiteID, .Text = item.Name})
            Next
            ViewData("selectSite") = selectSite

            Return View()
        End Function

        ' POST: Devices/Create
        'To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        'more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        <HttpPost()>
        <ValidateAntiForgeryToken()>
        Async Function Create(<Bind(Include:="DeviceID,Make,Model,Serial")> ByVal device As Device) As Task(Of ActionResult)
            If ModelState.IsValid Then
                'Because Site is selected in a DropDownFor which only passes the ID integer, need to set the device's site property accordingly
                'Note that with Site being a Navigation property, it looks like it doesn't get saved the way you would think...but it all works
                Dim thisSite As Integer = CInt(Request.Form.Get("Site"))
                device.Site = (From site In db.Sites Where site.SiteID = thisSite Select site).FirstOrDefault()
                db.Devices.Add(device)
                Await db.SaveChangesAsync()
                Return RedirectToAction("Index")
            End If
            Return View(device)
        End Function

        ' GET: Devices/Edit/5
        Async Function Edit(ByVal id As Integer?) As Task(Of ActionResult)
            If IsNothing(id) Then
                Return New HttpStatusCodeResult(HttpStatusCode.BadRequest)
            End If
            Dim device As Device = Await db.Devices.FindAsync(id)
            If IsNothing(device) Then
                Return HttpNotFound()
            Else
                'Create the list of items that the DropDownFor in the View will reference
                Dim selectSite As New List(Of SelectListItem)
                Dim db = New TestWebApplication2.Models.TestWebApplication2Context
                Dim listOfSites As List(Of Site)
                listOfSites = (From site In db.Sites Select site).ToList()

                For Each item In listOfSites
                    selectSite.Add(New SelectListItem() With {.Value = item.SiteID, .Text = item.Name})
                Next
                ViewData("selectSite") = selectSite
            End If

            Return View(device)
        End Function

        ' POST: Devices/Edit/5
        'To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        'more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        <HttpPost()>
        <ValidateAntiForgeryToken()>
        Async Function Edit(<Bind(Include:="DeviceID,Make,Model,Serial")> ByVal device As Device) As Task(Of ActionResult)
            If ModelState.IsValid Then
                'Because Site is selected in a DropDownFor which only passes the ID integer, need to set the device's site property accordingly
                'Note that with Site being a Navigation property, it looks like it doesn't get saved the way you would think...but it all works
                Dim thisSite As Integer = CInt(Request.Form.Get("Site"))
                device.Site = (From site In db.Sites Where site.SiteID = thisSite Select site).FirstOrDefault()
                db.Entry(device).State = EntityState.Modified
                Await db.SaveChangesAsync()
                Return RedirectToAction("Index")
            End If
            Return View(device)
        End Function

        ' GET: Devices/Delete/5
        Async Function Delete(ByVal id As Integer?) As Task(Of ActionResult)
            If IsNothing(id) Then
                Return New HttpStatusCodeResult(HttpStatusCode.BadRequest)
            End If
            Dim device As Device = Await db.Devices.FindAsync(id)
            If IsNothing(device) Then
                Return HttpNotFound()
            Else
                'Need to have this include Site using Predicate so that it will pull in that data in order to reference site name in the View
                device = db.Devices.Include("Site").Where(Function(x) x.DeviceID = id).FirstOrDefault()
            End If
            Return View(device)
        End Function

        ' POST: Devices/Delete/5
        <HttpPost()>
        <ActionName("Delete")>
        <ValidateAntiForgeryToken()>
        Async Function DeleteConfirmed(ByVal id As Integer) As Task(Of ActionResult)
            Dim device As Device = Await db.Devices.FindAsync(id)
            db.Devices.Remove(device)
            Await db.SaveChangesAsync()
            Return RedirectToAction("Index")
        End Function

        Protected Overrides Sub Dispose(ByVal disposing As Boolean)
            If (disposing) Then
                db.Dispose()
            End If
            MyBase.Dispose(disposing)
        End Sub