Mohammad Javahery
Mohammad Javahery

Reputation: 49

ASP.Net MVC ID field not filled in database

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):

enter image description here

This causes empty site name/ID when I load warehouses from database. How can I fix this?

Upvotes: 0

Views: 172

Answers (1)

Dai
Dai

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:

  • You should not be using EF entity classes as DTOs or ViewModels for form-bindings - it introduces security vulnerabilities ("overposting") as well as makes software harder to maintain because you can't version DTOs and forms separately from entity classes.
  • Validation is not verification. You should not be accessing your database directly from within your 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.
    • Validation is the process of ensuring submitted data conforms with some invariant constraints on that data (e.g. phone-number formatting, non-empty names, etc).
    • Verification involves doing actual leg-work to actually verify that submitted data is correct, instead of merely valid.
      • "Correct" data is a subset of the set of valid data.

Upvotes: 1

Related Questions