MVC pass two objects to view - vb.net

I am just struggling with passing two objects to my view and then show data out of it. On purpose to send two diffrent objects to a view i maked separated class for it which would contain my two class object to be pased as follows:
Public Class CustomModelProjetsTransports
Public Projects As IEnumerable(Of woitgroup_transport.tbProjekt)
Public Transports As IEnumerable(Of woitgroup_transport.tbTransport)
End Class
Now in my controlled i am pasing by it to a view (i checked and the data is there:
Function Index() As ActionResult
If Not IsNothing(Session("LogedUserId")) Then
Dim userId As Integer = Session("LogedUserId")
Dim projectsAndTransportsLists As New CustomModelProjetsTransports
Dim ProjectsPerUser As New List(Of tbProjekt)
ProjectsPerUser = db.Database.SqlQuery(Of tbProjekt)("SELECT * FROM [mydb].[dbo].[tbProjekt] where Id IN (SELECT DISTINCT ProjectId FROM [mydb].[dbo].[tbUserProject] WHERE UserId = " & Session("LogedUserId") & ")").ToList
Dim transportsPerUser As New List(Of tbTransport)
transportsPerUser = db.Database.SqlQuery(Of tbTransport)("SELECT * FROM [mydb].[dbo].[tbTransport] where ProjectId IN (SELECT DISTINCT ProjectId FROM [mydb].[dbo].[tbUserProject] WHERE UserId = " & Session("LogedUserId") & ")").ToList
projectsAndTransportsLists.Projects = ProjectsPerUser
projectsAndTransportsLists.Transports = transportsPerUser
Return View(projectsAndTransportsLists)
Else
Return RedirectToAction("Index", "Login")
End If
End Function
problem is here within View:
this is on top:
#ModelType IEnumerable(Of woitgroup_transport.CustomModelProjetsTransports)
<tbody>
#For Each item In Model(1).Transports.ToList
Dim currentItem = item
#<tr>
...
when i am executing my application its filling out correctly here:
Return View(projectsAndTransportsLists)
but then i am reciving an error when view wants to be shown as follows:
The model item passed into the dictionary is of type 'woitgroup_transport.CustomModelProjetsTransports', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1[woitgroup_transport.CustomModelProjetsTransports]'.
Additional question:
Kepping on mind i have Projects list within my view how to correctly fill in dropdown (the best using bootstrap styles). I am trying like that but still something wrong...
#Html.DropDownListFor(Function(m) m.Projects.., DirectCast(Model.Projects, SelectList), New With { _
.class = "form-control" _
})
its also saying that Function(m) m.Projects.Name
that Name is not exist - i see only generic list on intelisense like Function(m) m.Projects.ToList ...

The error message is quite clear about the problem. You are passing a CustomModelProjetsTransports to the view. But the view expects a IEnumerable(Of CustomModelProjetsTransports). Remove the IEnumerable and you should be fine.

Related

How to bind the selected member of a ComboBox?

I have a ComboBox that is populated with objects of type ProfileName
Private Class ProfileName
Public Property Name As String
Public Property File As String
Public Property ProductVersion As String
End Class
These items are created added to the combo box after a deserialising a bunch of files and copying some of the values from the resulting objects:
pcb.DisplayMember = "Name"
For Each F As FileInfo In ProfileFiles
Dim Reader As StreamReader = F.OpenText()
Dim Serialize As Serializer = New Serializer()
Dim SerializedData As String = Reader.ReadToEnd()
Dim P As Profile = Serialize.DesearializeObject(Of Profile)(SerializedData)
If P.Type = Profile.ProfileType.Product Then
Dim PN As ProfileName = New ProfileName()
PN.File = F.Name
PN.ProductVersion = P.ProductVersion
PN.Name = P.ProductName & " - " & P.ProductVersion
pcb.Items.Add(PN)
End If
Reader.Close()
Next
Then if a user opens one of these files, the file will be again deserialised resulting in a Profile object with a 'ProductName' property that should match one of the items already on the ComboBox items list, so I'd like for the ComboBox to show that as the selected item.
i.e.
-On form load the ComboBox is populated with all possible product names.
-When a profile file is opened the product that the profile uses is automatically selected in the ComboBox.
I've been playing with
ProductComboBox.DataBindings.Add("SelectedValue", CurrentProfile, "ProductName")
and permutations thereof, but can't seem to get it right.
You cant mix and match - put objects into the items collection and use the data binding methods/elements. Databinding basics:
Public Class Profile
Public Property Name As String
Public Property File As String
Public Property ProductVersion As String
Public Overrides Function ToString() As String
Return String.Format("{0} ({1})", Name, ProductVersion)
End Function
End Class
The ToString() controls what will be displayed when you cant specify the property to display. Note, these should be properties becaus mere Fields will be treated differently.
Then a container for them. This will be the DataSource for the cbo.
Private Profiles As List(Of Profile)
...
' create instance of list and populate from where ever:
Profiles = New List(Of Profile)
Profiles.Add(New Profile With {.Name = "Default", .File = "foo",
.ProductVersion = "1.0"})
Profiles.Add(New Profile With {.Name = "Ziggy", .File = "bat",
.ProductVersion = "1.9.8"})
Profiles.Add(New Profile With {.Name = "Zoey", .File = "bar",
.ProductVersion = "1.4.1"})
Rather than putting the Profile objects into the Items collection, bind the control to the List:
cboP.DataSource = Profiles
cboP.DisplayMember = "Name"
If you omit the property to display, ToString() will be shown (or WindowsApp1.Profile if you did not override it). Note: When using a DataSource you no longer add or delete from the control's Items collection - it will yell at you. Instead manage the underlying source, your List(Of Profile) in this case.
To change the selection, for example to the one for "Ziggy":
Dim n As Int32 = Profiles.FindIndex(Function(f) f.Name = "Ziggy")
If n > -1 Then
cboP.SelectedIndex = n
End If
You can also set SelectedItem after you find the Profile instead, but I tend to use index. Even though the list is a new actor, serializing the entire thing is easy:
' serializing the List acts on all the profiles in it
Dim json = JsonConvert.SerializeObject(Profiles)
File.WriteAllText("C:\Temp\Profiles.json", json)
Read it back:
json = File.ReadAllText("C:\Temp\Profiles.json")
Dim newPs = JsonConvert.DeserializeObject(Of List(Of Profile))(json)
Its a bit simpler than looping thru a set of files. List(of T) has a full set of methods and extensions to remove, sort, find etc so you should gain functionality over the items collection or array.
Alternatively, you could keep the one file per structure, but add the deserialized Profile objects to a List(of Profile) rather than the Items collection.

List (Of T) as DataGridView.DataSource makes sorting fail

I have read some threads about this "error" but I can't figure out how to solve my problem.
I have a class that looks something like this:
Public Class Person
Public Property Name As String
Public Property PhoneNumber As string
Public Property Age As Integer
Public sub New(ByVal Values As String())
Me.Name = Values(0)
Me.PhoneNumber = Values(1)
Me.Age = Convert.ToInt32(Values(2))
End Sub
End Class
I get my data from a semicolon separated file, and i create a list of Person objects by looping this file and split on semicolon. Like this
Dim PersonsList As New List(Of Person)
For Each line in textfile..........
PersonsList.Add(New Person(line.Split(";")))
Next
When the list is complete, I tell my DataGridView that DataSource is PersonsList.
This works like a charm, but I'm not able to sort the columns.
I found this post amongst many (where the class values are not properties, which mine are) and tried that converting function which did'nt really work in my case. The right amount of rows were created, but all of the columns were blank.
What am I missing?
If you use a datatable as the data source, column sorting is automatically enabled and you can sort the data by any column:
Dim dt As New DataTable
dt.Columns.AddRange(
{
New DataColumn("Name"),
New DataColumn("Phone"),
New DataColumn("Age")
})
For Each s As String In IO.File.ReadAllLines("textfile1.txt")
Dim temprow As DataRow = dt.NewRow
temprow.ItemArray = s.Split(";"c)
dt.Rows.Add(temprow)
Next
DataGridView1.DataSource = dt

Create ReadOnly Property of CSV String vb.net

What is the best way to go about taking a List(Of objects) and adding a ReadOnly Property that displays a csv list of one of the Properties of the object? Is there a good way to convert that list to a string by specifying "Name".
Ex
Object = New Item()
Object.ID = 1
Object.Name = "Test"
li.Add(Object)
Object = New Item()
Object.ID = 2
Object.Name = "Test 2"
li.Add(Object)
Object = New Item()
Object.ID = 3
Object.Name = "Test 3"
li.Add(Object)
I'm thinking a for each would need to be done here or is there a better way to do this?
Return "Test, Test 2, Test 3" from the list values
Essentially, this does what I'm wanting to do but I would like to know if there is a BETTER way to do it.:
Public ReadOnly Property ItemList() As String
Get
Dim returnvalue As String = String.Empty
If Items.Count > 0 Then
For Each Item In Items
returnvalue = returnvalue & Item.Name & ", "
Next
Return Left(returnvalue, returnvalue.Length - 2)
Else
Return ""
End If
End Get
End Property
I have this link to share, the solution is similar, and may need a few tweeks. The conversion from C# to VB is simple enough and can show you where to make a few changes.
Write C# Lists of objects in CSV file
However, I believe your code is fine, with the exception of how you create your csv string.
I would recommend using string builder. This way, if an item exists, you build a new line. Otherwise, you don't. This would keep your from needed to remove (left) the last inserted comma. You would also want to catch the last item in your list, and avoid the comma at the end there.
as an exmaple:
Dim sb As StringBuilder
--if not last item then --
sb.AppendFormat ("{0},{1}",Item.Name,",")
--else this is last item--
sb.AppendFormat ("{0},",Item.Name)
The link I shared is a much better solution. My example is only to build your method more reliable.
Or, if you change your list of object to an array, or list of string...
Dim sampleList = New List(Of String)() From { _
"lala", _
"lulu", _
"lele" _
}
Dim data = String.Join(",", sampleList)

How to bind a DataGridView to a list of custom classes

I have a list of custom classes that I am building using a TableAdapter.
I want to add these to a DataGridView binding certain columns only.
I have tried the code below to fill and bind the data:
lAllBookings = (From r As DataRow In BookingsTableAdapter1.GetDataWithItems().Rows
Select New Booking With {.bookingID = r.Item("BookingID"), _
.itemID = r.Item("ItemID"), _
.bookedOutDate = r.Field(Of DateTime?)("BookedOutDate"), _
.bookedInDate = r.Field(Of DateTime?)("BookedInDate"), _
.identType = r.Item("IdentType"), _
.identString = r.Item("IdentString"), _
.image = r.Item("Image"), _
.complete = r.Item("Complete"), _
.notes = r.Item("Notes"), _
.itemName = r.Item("ItemName"), _
.itemBC = r.Item("ItemBarcode")}).ToList
dgvBookings.Columns("BookingID").DataPropertyName = "bookingID"
dgvBookings.Columns("ItemIdent").DataPropertyName = "itemName"
dgvBookings.Columns("BookedOut").DataPropertyName = "bookedOutDate"
dgvBookings.Columns("IdentString").DataPropertyName = "identString"
dgvBookings.DataSource = lAllBookings
Now when I do this I get the correct number of rows but all fields are blank.
I've run through a few questions on SO and a few tutorials but they all seem to do things slightly different to what I need.
Is there a way I can fill the DataGridView using my list of items?
I'd rather avoid using a DataSet if I can as I've built a lot of other code on this List<Of Class> type.
Edit - Here is the Class Booking declaration:
Public Class Booking
Public bookingID As Integer
Public itemID As Integer
Public itemName As String
Public itemBC As String
Public identType As Short
Public identString As String
Public image As Byte()
Public complete As Boolean
Public notes As String
Public bookedInDate As DateTime?
Public bookedOutDate As DateTime?
End Class
I know is really late for you but maybe it can help someone.
I had the same problem that you but using vb.net.
Finally I found the solution: Your class was not exposing properties but variables. The binding system looks for properties and can't find them so you get the empty rows.
Try to complete your class adding {get; set;} (or the Property attribute if using vb.net) and everything will work.
Hope it can be helpful.
I am not sure that you are getting in IAllBookings all the information you want. In any case, you are not passing it rightly to the dgvBookings. Here you have a couple of small codes to help you to understand how this works better:
dgvBookings.Columns.Clear()
Dim newTable As New DataTable
newTable.Columns.Add("Column1")
newTable.Columns.Add("Column2")
newTable.Columns.Add("Column3")
newTable.Rows.Add("1", "2", "3")
newTable.Rows.Add("1", "2", "3")
newTable.Rows.Add("1", "2", "3")
dgvBookings.DataSource = newTable
The newTable emulates perfectly the DataGridView structure (columns & rows) and thus it can be given as a DataSource directly.
Unlikely the case of a simple List:
dgvBookings.Columns.Clear()
Dim newList = New List(Of String)
newList.Add("1")
newList.Add("2")
newList.Add("3")
dgvBookings.DataSource = newList
You are providing less information than expected (1D vs. the expected 2D) and thus the result is not the one you want. You need to provide more information; for example: instead of relying on DataSource, you might add the rows one by one:
dgvBookings.Columns.Add("Column1", "Column1")
For Each item In newList
dgvBookings.Rows.Add(item)
Next
I hope that this answer will help you to understand better how to deal with DataGridView and with the different data sources.
-- UPDATE
Row by row option applied to your specific case.
For Each item As Booking In lAllBookings
With item
dgvBookings.Rows.Add(.bookingID.ToString(), .itemID.ToString(), .bookedOutDate.ToString(), .identString.ToString())
End With
Next

Default Value For Dropdown List

I need to set a default value for the DropDown list as follows:
#Html.DropDownList("BillId", "")
The user doesn't necessarily need to select something but the list throws an error
The ViewData item that has the key 'BillId' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'
if a value is not selected and the box is left on it's default blank state.
My controllers as follows:
Function Create(id As Integer) As ViewResult
ViewBag.id = id
Dim job As Job = New Job
job.CustomerId = id
job.JobAmount = 0
job.JobDate = Date.Now()
job.JobStatus = "Active"
Dim BillList = New List(Of Bill)()
Dim BillQuery = From s In db.Bills
Select s
BillList.AddRange(BillQuery)
ViewBag.BillId = New SelectList(BillList, "BillId", "BillDate")
Return View(job)
End Function
'
' POST: /Job/Create
<HttpPost()>
Function Create(job As Job) As ActionResult
If ModelState.IsValid Then
db.Jobs.Add(job)
db.SaveChanges()
Dim customer As Customer = db.Customers.Find(job.CustomerId)
Dim customerNumber As String = customer.CustCellphone.ToString()
Dim messageSender As SendMessage = New SendMessage
Dim smsMessage As String = "LAUNDRY: Job Number " & job.JobId & " has been booked in. You will be notified when individual services within it are ready for collection."
messageSender.SendMessage(smsMessage, customerNumber)
Dim url As String = "/RequestedService/AddService/" + job.JobId.ToString()
Return Redirect(url)
End If
Return View(job)
End Function
If your model Job doesn't have the property of BillId than in the view the dropdown markup should be
#Html.DropDownList("Job_BillId", ViewBag.BillId)
And the model Job contains the property BillId than create the ViewBag with some different name and use them accordingly in view markup for example:
Create a ViewBag with different name instead of BillId in controller
ViewBag.BillIdList = New SelectList(BillList, "BillId", "BillDate")
And in view use this as below
#Html.DropDownListFor(model => model.BillId, ViewBag.BillIdList)
Hope this will help you.