Reputation: 49
I'm almost new to MVC and trying to create a simple app for data management (MVC 5/VS 2022) using this tutorial. I have two classes along with their controllers and views: Site and Warehouse. Warehouse is site-dependent, so this is its model:
Public Class Warehouse
Implements IValidatableObject
Private mId As Integer
Private mSite As Site
Private mWarehouseID As String
Private mWarehouseName As String
<Display(Name:="Site"), Required>
Public SiteID As Integer
<Key>
Public Property Id As Integer
Get
Return mId
End Get
Set(value As Integer)
mId = value
End Set
End Property
<Display(Name:="Site")>
Public Overridable Property Site As Site
Get
Return mSite
End Get
Set(value As Site)
mSite = value
End Set
End Property
<Display(Name:="Warehouse ID"), Required, Index(IsUnique:=True), MaxLength(20, ErrorMessage:="Maximum allowed length is 20 characters")>
Public Property WarehouseID As String
Get
Return mWarehouseID
End Get
Set(value As String)
mWarehouseID = value
End Set
End Property
<Display(Name:="Warehouse Name"), Required, MaxLength(100, ErrorMessage:="Maximum allowed length is 100 characters")>
Public Property WarehouseName As String
Get
Return mWarehouseName
End Get
Set(value As String)
mWarehouseName = value
End Set
End Property
Public Function Validate(validationContext As ValidationContext) As IEnumerable(Of ValidationResult) Implements IValidatableObject.Validate
Dim db As New Data.KFI_IPPContext
Dim vresult As New List(Of ValidationResult)
Dim validatename = db.Warehouses.FirstOrDefault(Function(w) (w.WarehouseID = WarehouseID) And (w.Id <> Id))
If validatename IsNot Nothing Then
Dim errmsg As New ValidationResult($"Warehouse {WarehouseID} already exists.")
vresult.Add(errmsg)
End If
Return vresult
End Function
End Class
And this is controller:
Public Class WarehousesController
Inherits System.Web.Mvc.Controller
Private db As New KFI_IPPContext
' GET: Warehouses
Public Async Function Index(SearchString As String) As Threading.Tasks.Task(Of ActionResult)
Dim warehouses = From w In db.Warehouses
If String.IsNullOrEmpty(SearchString) Then
warehouses = From w In warehouses Select w
Else
warehouses = From w In warehouses Where w.WarehouseName.Contains(SearchString) Or w.WarehouseID.Contains(SearchString) Or w.Site.SiteName.Contains(SearchString)
End If
Return View(Await warehouses.ToListAsync)
End Function
' GET: Warehouses/Details/5
Function Details(ByVal id As Integer?) As ActionResult
If IsNothing(id) Then
Return New HttpStatusCodeResult(HttpStatusCode.BadRequest)
End If
Dim warehouse As Warehouse = db.Warehouses.Find(id)
If IsNothing(warehouse) Then
Return HttpNotFound()
End If
Return View(warehouse)
End Function
' GET: Warehouses/Create
Function Create() As ActionResult
PopulateSiteList()
Return View()
End Function
' POST: Warehouses/Create
'To protect from overposting attacks, enable the specific properties you want to bind to, for
'more details see https://go.microsoft.com/fwlink/?LinkId=317598.
<HttpPost()>
<ValidateAntiForgeryToken()>
Function Create(<Bind(Include:="Id,WarehouseID,WarehouseName")> ByVal warehouse As Warehouse) As ActionResult
If ModelState.IsValid Then
db.Warehouses.Add(warehouse)
db.SaveChanges()
Return RedirectToAction("Index")
End If
PopulateSiteList(warehouse.SiteID)
Return View(warehouse)
End Function
' GET: Warehouses/Edit/5
Function Edit(ByVal id As Integer?) As ActionResult
If IsNothing(id) Then
Return New HttpStatusCodeResult(HttpStatusCode.BadRequest)
End If
Dim warehouse As Warehouse = db.Warehouses.Find(id)
If IsNothing(warehouse) Then
Return HttpNotFound()
End If
PopulateSiteList(warehouse.SiteID)
Return View(warehouse)
End Function
' POST: Warehouses/Edit/5
'To protect from overposting attacks, enable the specific properties you want to bind to, for
'more details see https://go.microsoft.com/fwlink/?LinkId=317598.
<HttpPost()>
<ValidateAntiForgeryToken()>
Function Edit(<Bind(Include:="Id,WarehouseID,WarehouseName")> ByVal warehouse As Warehouse) As ActionResult
If ModelState.IsValid Then
db.Entry(warehouse).State = EntityState.Modified
db.SaveChanges()
Return RedirectToAction("Index")
End If
PopulateSiteList(warehouse.SiteID)
Return View(warehouse)
End Function
' GET: Warehouses/Delete/5
Function Delete(ByVal id As Integer?) As ActionResult
If IsNothing(id) Then
Return New HttpStatusCodeResult(HttpStatusCode.BadRequest)
End If
Dim warehouse As Warehouse = db.Warehouses.Find(id)
If IsNothing(warehouse) Then
Return HttpNotFound()
End If
Return View(warehouse)
End Function
' POST: Warehouses/Delete/5
<HttpPost()>
<ActionName("Delete")>
<ValidateAntiForgeryToken()>
Function DeleteConfirmed(ByVal id As Integer) As ActionResult
Dim warehouse As Warehouse = db.Warehouses.Find(id)
db.Warehouses.Remove(warehouse)
db.SaveChanges()
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
Private Sub PopulateSiteList(Optional SelectedSite As Object = Nothing)
Dim sitequery = From s In db.Sites Order By s.SiteID Select s
ViewData("SiteID") = New SelectList(sitequery, "SiteID", "SiteName", SelectedSite)
End Sub
End Class
And this is view for creating warehouses (Only "Using" part):
@Using (Html.BeginForm())
@Html.AntiForgeryToken()
@<div class="form-horizontal">
<h4>Warehouse</h4>
<hr />
@Html.ValidationSummary(True, "", New With {.class = "text-danger"})
<div class="form-group">
<label for="SiteID" class="custom-select150">Site</label>
<div class="col-md-10">
@Html.DropDownList("SiteID", Nothing, htmlAttributes:=New With {.class = "custom-select150"})
@Html.ValidationMessageFor(Function(model) model.SiteID, "", New With {.class = "text-danger"})
</div>
</div>
<div class="form-group">
@Html.LabelFor(Function(model) model.WarehouseID, htmlAttributes:=New With {.class = "control-label col-md-2"})
<div class="col-md-10">
@Html.EditorFor(Function(model) model.WarehouseID, New With {.htmlAttributes = New With {.class = "form-control"}})
@Html.ValidationMessageFor(Function(model) model.WarehouseID, "", New With {.class = "text-danger"})
</div>
</div>
<div class="form-group">
@Html.LabelFor(Function(model) model.WarehouseName, htmlAttributes:=New With {.class = "control-label col-md-2"})
<div class="col-md-10">
@Html.EditorFor(Function(model) model.WarehouseName, New With {.htmlAttributes = New With {.class = "form-control"}})
@Html.ValidationMessageFor(Function(model) model.WarehouseName, "", New With {.class = "text-danger"})
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
End Using
Now the problem is that when I add a new warehouse, Site_Id is NULL in database (I have filled the first one manually via SSMS):
This causes empty site name/ID when I load warehouses from database. How can I fix this?
Upvotes: 0
Views: 172
Reputation: 155075
Entity Framework only binds database TABLE
and VIEW
columns to properties, not fields (EF Core does support updating fields directly, but I assume you're using EF, not EF Core, because to my knowledge EF Core does not support VB.NET).
Change your class' member from this....
<Display(Name:="Site"), Required>
Public SiteID As Integer
...to this:
<Display(Name:="Site"), Required>
Public Property SiteId As Integer
...or this (if you're a verbose-code masochist...):
Private mSiteId As Integer
' [etc]
<Display(Name:="Site"), Required>
Public Property SiteId As Integer
Get
Return mSiteId
End Get
Set(value As Integer)
mSiteId = value
End Set
End Property
Other points:
IValidatableObject.Validate
method: that's the responsibility of your Controller Action or some other part of your application's ASP.NET requesty-processing pipeline - the Validate
method is intended to be synchronous and fast which implies you should not be doing any IO in there.
Upvotes: 1