包含不同模型

时间:2015-10-02 17:59:07

标签: asp.net-mvc vb.net dropdownlistfor

我对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

如您所见,设备已链接到网站。在数据库上下文中,Devices表包含外键“Site_SiteID”,如下所示:

DB Context

我要做的是在“创建和编辑的设备”视图中,使“站点”字段为DropDownListFor,其列表包含“站点”表中存在的站点。然后将所选站点保存为设备的站点。

这是我在Create视图中创建的列表,该列表将在下拉列表中...后跟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中的创建帖子:

    <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

这可以使我在“创建”页面的下拉列表中获取站点列表,但是当我点击“保存”时,它会给出一个错误,例如“值'1'无效”。这是因为它试图传入一个字符串(这是列表项的值的类型)而不是一个站点。

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

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

但是它给了我一个错误,它不能将Site转换为String,因此你无法传回整个对象。

然后我尝试通过控制器中的ID来设置设备的站点,就像这样(注意“站点”是从绑定列表中取出):

    <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

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

我认为必须有办法做到这一点,但我不确定还有什么可以尝试的。任何帮助表示赞赏!

2 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分:0)

直到现在我还没有意识到这个名为"Navigation Property"的东西。因此SiteDevice的属性,它是自己的模型,将两个表链接在一起。

所以我错过了,正如史蒂夫·格林(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