Edit one more time:
So it looks like I have figured out the address part too, in the class I have:
Public ReadOnly Property addresses As IEnumerable(Of Address)
Get
Return _these_addresses
End Get
End Property
And in the template I have:
<% For Each item In Model.addresses%>
<tr>
<td>
<a href='<%: Url.Action("Edit", "Address", New With { .pid=Model.ContactID, .id=item.AddressID }) %>'>
<img src='<%: Url.Content("~/Content/Images/Edit.jpg") %>' alt="Edit" />
</a>
<a href='<%: Url.Action("Details", "Address", New With { .pid=Model.ContactID, .id=item.AddressID }) %>'>
<img src='<%: Url.Content("~/Content/Images/Detail.jpg") %>' alt="Details" />
</a>
<a href='<%: Url.Action("Delete", "Address", New With { .pid=Model.ContactID, .id=item.AddressID }) %>'>
<img src='<%: Url.Content("~/Content/Images/Delete.jpg") %>' alt="Delete" />
</a>
</td>
<td>
<%: item.Street%>
</td>
<td>
<%: item.City%>
</td>
<td>
<%: item.StateID%>
</td>
<td>
<%: item.CountryID%>
</td>
<td>
<%: item.Zip%>
</td>
</tr>
<% Next%>
</table>
Edit Again:
I added this for every field in contact to the class and it is working...I just need to figure out the list of addresses now...
Public ReadOnly Property FirstName() As String
Get
Return _this_contact.FirstName
End Get
End Property
E D I T:
I about have it figured out:
I took a shot in the dark and made a ContactViewModel based of C examples I have found in research on how to do this
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Web
Imports TotallyAwesomeCRM.Contact
Imports TotallyAwesomeCRM.Address
Public Class ContactViewModel
Public contact As Contact
Public address As Address
Private _this_contact As Contact
Private _these_addresses As System.Linq.IQueryable(Of Address)
Sub New(ByVal this_contact As Contact, ByVal these_addresses As System.Linq.IQueryable(Of Address))
' TODO: Complete member initialization -this was added by the framework for me when I tried to call this class - I don't know what to do here - resources?
_this_contact = this_contact
_these_addresses = these_addresses
End Sub
End Class
So in my controller I:
Function Details(ByVal id As Integer) As ActionResult
Dim this_contact = GetContact(id)
Dim these_addresses =
From address In addressDataContext.Addresses, xref In addressDataContext.ContactAddressesXrefs
Where address.AddressID = xref.AddressID And xref.ContactID = id
Select address
Dim viewModel = New ContactViewModel(this_contact, these_addresses)
Return View(viewModel)
End Function
And in the template it found the contact when I started typing Model.contact
<%: Model.contact.FirstName%>
But it gave me an error there: NullReferenceException: Object reference not set to an instance of an object.
It shouldn't be null...Please help me figure out the TODO
=================================================================================
O R I G I N A L P O S T:
This is my first .NET venture ever. Everything I have done thus far I have figured out.
OK I have contacts, and contacts can have many addresses. I would like to display those addresses when I am viewing the detail of a Contact I would also like to have a container that is a list view of it's addresses. From that list view I want to be able to edit/delete/view the address.
My database structure is:
Tables:
Contacts
<contains contact info and PK>
Addresses
<contains address info and PK>
ContactAddressesXref
ContactID
AddressID
I have basically been editing the skeleton files the ASP.NET MVC empty application provides me
So here is the last thing I have tried in my Contact controller:
'
' GET: /Contacts/Details/5
Function Details(ByVal id As Integer) As ActionResult
Dim this_contact =
From contact In dataContext.Contacts, address In addressDataContext.Addresses, xref In addressDataContext.ContactAddressesXrefs
Where address.AddressID = xref.AddressID And xref.ContactID = id
Select contact, address
Return (View(this_contact))
End Function
What I really wanted to do was query the addresses separately and send them in as their own set. I don't know if doing this is the right thing to do, but when I tried to send in
two models it freaked out.
Which fails of course because the view has this in it:
<%# Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage(Of IEnumerable (Of TotallyAwesomeCRM.Contact))" %>
I tried:
Inherits="System.Web.Mvc.ViewPage(Of IEnumerable (Of TotallyAwesomeCRM.Contact, Of TotallyAwesomeCRM.Address))
It said:
'InitializeCulture' is not a member of 'ASP.views_contacts_details_aspx'.
So OK I tried:
Inherits="System.Web.Mvc.ViewPage"
And it throws and error here:
<div class="display-field"><%: Model.FirstName%></div>
Of course, so Am I wrong in the controller? I know I can't have just System.Web.Mvc.ViewPage(Of IEnumerable (Of TotallyAwesomeCRM.Contact)) that it will have to accept more than that. I tried jacking with the Model.FirstName part saying Contact.FirstName, but that didn't come up in the drop down when I started writing Contact. I could do this easily in other languages, .Net seems to be a different ball game. Please help!
You don't need "todo: complete member initialization..." (besides remove it :-)). You need to change the property for contact and address.
Try this:
Public Class ContactViewModel
Private _this_contact As Contact
Private _these_addresses As System.Linq.IQueryable(Of Address)
Sub New(ByVal this_contact As Contact, ByVal these_addresses As System.Linq.IQueryable(Of Address))
' TODO: Complete member initialization -this was added by the framework for me when I tried to call this class - I don't know what to do here - resources?
_this_contact = this_contact
_these_addresses = these_addresses
End Sub
Public ReadOnly Property Contact As Contact
Get
Return _this_contact
End Get
End Property
Public ReadOnly Property Addresses As System.Linq.IQueryable(Of Address)
Get
Return _these_addresses
End Get
End Property
End Class
Related
As mentioned in my previous post, I'm working on a small Grails CRUD app. In my app I have a "Master Agreement" model that has a hasMany relationship with a model called "SOW" (scope of work). I have a view called "show" under Master Agreeements that shows the administrative information for the selected Master Agreement. At the bottom of the page I would like to show each SOW associated with the master agreement. Below is my code along with the actions I've taken to try and solve this.
Show Action in Master Agreement Controller:
The Master Agreement model is successfully passed to the Master Agreement "show" view and Grails successfully prints the sowList to the console. However, when I reference the SOW model in the view, nothing happens.
def show(Long id){
println 'I made it to show for Master Agreement.'
def masterAgreementInstance = MasterAgreement.get(id)
def sowList = masterAgreementInstance.getSows()
println sowList
render(view: "show", model: [masterAgreement: masterAgreementInstance, sow: sowList])
}
Master Agreement "Show" View
The HTML is rather long, so below is how I reference the Master Agreement model (this works), followed by how I am trying to reference the SOW model (this doesn't work - the page loads, but nothing happens with the sow reference). In fact, no rows show up at all under my table headers.
<div class="grid-item">
<div class="label">Office:</div>
<div class="data">${masterAgreement.office.name}</div>
</div>
Unsuccessful SOW Model Reference - (see ${sow.name} and ${sow.spendCap} )
<table class="infoTable fullWidth">
<tr>
<th>#</th>
<th>SOW Name</th>
<th>Invoice Total</th>
<th>SOW Amount</th>
<th>View</th>
<th class="centered">Expand</th>
</tr>
<g:each status="i" var="sow" in="${sow}">
<tr>
<td>${i + 1}</td>
<td>${sow.name}</td>
<td>Placeholder data</td>
<td>${sow.spendCap}</td>
<td>
<g:link controller="SOW" action="show">View</g:link>
</td>
<td class="centered">
<i class="fas fa-angle-left" id="expandArrow" onclick="expandInvoices();" title="Expand Invoices"></i>
</td>
</tr>
</g:each>
</table>
Below are the two models.
class MasterAgreement {
String name
String pointOfContactFName
String pointOfContactLName
String pointOfContactPhone
String pointOfContactEmail
Double spendCap
Date startDate
Date endDate
Office office
BidType bidType
MasterAgreementStatus masterAgreementStatus
static hasMany = [sows: SOW]
static belongsTo = [contractor: Contractor] //will cascade on Contractor delete
static constraints = {
}
}
class SOW {
String name
Double spendCap
static hasMany = [sowInvoices: SOWInvoice]
static belongsTo = [masterAgreement: MasterAgreement] //will cascade on Contractor and/or MasterAgreement delete
static constraints = {
}
}
How the page looks upon loading.
enter image description here
Any help is greatly appreciated!
This is a very basic model/view/controller and the model is not coming from the view with any data to the controller. It is probably something basic that I am missing!
Two Models:
Namespace Models
Public Class Search
Public Property Search() As String = String.Empty
Public Property Member() As Member = Nothing
End Class
End Namespace
Namespace Models
Public Class Member
Public Property ContactRefID() As String = String.Empty
End Class
End Namespace
Controller:
Imports Test.Models
Imports Test.Services
Namespace Test
Public Class HomeController
Inherits System.Web.Mvc.Controller
Function Index() As ActionResult
Dim search As New Search
Return View("Index", search)
End Function
<HttpPost()>
Function Index(ByVal search As Search) As ActionResult
Dim member As New Member
Dim homeRepository As New HomeRepository
member = homeRepository.GetMemberData(search)
search.Member = member
Return View("Index", search)
End Function
End Class
End Namespace
View:
#ModelType Test.Models.Search
#Code
ViewData("Title") = "Search Page"
End Code
#Using Html.BeginForm("Index", "Home", FormMethod.Post)
#<div class="row">
<div class="col-md-4">
<label>Search Member/Account Number:</label>
#Html.TextBoxFor(Function(model) model.Search)
<br /><br />
<button id="btnSubmit" type="submit" class="btn btn-primary btn-large clrButton">
Search
</button>
</div>
</div>
<div class="row">
#*...To be filled with Search.Member data*#
</div>
End Using
When I enter a member/account number in the textbox to initiate the search, the search value does not get sent to the 2nd Index function in the controller. It definitely gets called because my breakpoint inside of it gets hit, but the "ByVal search As Search" comes back as Nothing.
I know in 2013 that the get:set items are not needed for the Models, but I tried to add them and it still didn't work.
I also set a value for search.Search in the top index and the textbox gets populated with the value. But when I click the Search button, it doesn't get sent to the 2nd index function either.
Any suggestions?
DefaultModelBinder is unable to bind/recognize a property if the type has the same name - in this case Search. Just change the property to something else and you're good to go...
Generally it's a good practice to think through the objects you're going to create and avoid such tautology. It will be easier for you to maintain these objects if they sounds reasonable. E.g. in this case, you use the name Search for your class. There is no sense to name the sought value Search too. Just name it as it should - SoughtValue
I have two nearly identical scenarios regarding model binding on a controller action. One works, and one doesn't. I can't figure out why.
This works:
Given this ViewModel class:
Public Class SeasonCreateViewModel
Public Property Season As Season
End Class
We have these actions
Function Create() As ActionResult
Dim seasonVM As New SeasonCreateViewModel()
Return View("Create", seasonVM)
End Function
<HttpPost()>
<ValidateAntiForgeryToken()>
Function Create(seasonVM As SeasonCreateViewModel) As ActionResult
End Function
And everything binds perfectly. seasonVM.Season contains the values posted from the form.
HOWEVER, this doesn't work:
Given this ViewModel class:
Public Class UserCreateViewModel
Public UserPerson As UserPersonModel
End Class
And these actions:
Function Create() As ActionResult
Dim userVM As New UserCreateViewModel()
Return View("Create", userVM)
End Function
'
' POST: /Admin/User/Create
<HttpPost()>
<ValidateAntiForgeryToken()>
Function Create(userVM As UserCreateViewModel) As ActionResult
End Function
userVM.UserPerson does not bind to the form values the same way seasonVM.Season does. In fact, it is Nothing (aka. null)
Does anyone have any ideas?
If you're curious about the views, they are structured identically, as in:
#Using Html.BeginForm()
#Html.AntiForgeryToken()
#Html.ValidationSummary(True)
<div class="editor-label">
#Html.LabelFor(Function(model) model.UserPerson.NewUsername)
</div>
<div class="editor-field">
#Html.EditorFor(Function(model) model.UserPerson.NewUsername)
#Html.ValidationMessageFor(Function(model) model.UserPerson.NewUsername)
</div>
<p>
<input type="submit" value="Create" />
</p>
End Using
AND
#Using Html.BeginForm()
#Html.AntiForgeryToken()
#Html.ValidationSummary(True)
<div class="editor-label">
#Html.LabelFor(Function(model) model.Season.SeasonDescription)
</div>
<div class="editor-field">
#Html.EditorFor(Function(model) model.Season.SeasonDescription)
#Html.ValidationMessageFor(Function(model) model.Season.SeasonDescription)
</div>
<p>
<input type="submit" value="Create" />
</p>
End Using
Just a note: I've omitted irrelevant code, mostly just additional properties on the view pages. I will say there is no property named "userVM" on my UserPersonModel as was the case here: Model is null when form submitted
UPDATE
OK. I think I'm about ready to give up on figuring out why Season is binding properly, but UserPerson is not.
I thought I had figured out the answer, but it didn't seem to actually make a difference:
I have
Public Class SeasonCreateViewModel
Public Property Season As Season
End Class
and I have
Public Class UserCreateViewModel
Public UserPerson As UserPersonModel
End Class
When lined up like this, the difference seems obvious. In SeasonCreateViewModel, I have a property Season identically named to the class it is an instance of (Season). In UserCreateViewModel, I have a property UserPerson, which is named slightly differently from its class UserPersonModel. Because of this, I thought the model binder does not automatically match userVM.UserPerson to its corresponding class.
So I changed the class UserPersonModel to UserPerson so the Form values would match up in the same way they do for Season (ie, to the classname), but it STILL did not fix it.
What does fix it, however, is if I change this:
Function Create(userVM As UserCreateViewModel) As ActionResult
to this
Function Create(userPerson As UserPerson) As ActionResult
Why this suddenly binds properly, where it didn't before? I have no idea. Does this help anyone answer this question, though?
As I understand it, you are not creating a new instance of UserPersonModel. I could be very wrong though :)
Public Class UserCreateViewModel
Public UserPerson As UserPersonModel
End Class
I think in your action, you should do something like:
Function Create() As ActionResult
Dim userVM As New UserCreateViewModel()
//userVM.UserPerson = new UserPersonModel() -- in c#
//userVM.UserPerson As New UserPersonModel() -- in VB?
Return View("Create", userVM)
End Function
Or just ignore this answer :P
I've ran into this before and the answer is not at all obvious, but makes sense after you know. If you bind an entire model (rather than a static type), then every single member of that model must be posted in the form, otherwise, the modelbinder doesn't recognize the posted values as an instance of the model.
If you don't want to post every member, but rather just a subset, then you need to create a view model with just the fields you want to post, and use something like AutoMapper or just manually map the fields on the actual model in your POST action.
I get the following error when returning a view:
Server Error in '/' Application.
--------------------------------------------------------------------------------
The view 'student' or its master was not found. The following locations were searched:
~/Views/Student/student.aspx
~/Views/Student/student.ascx
~/Views/Shared/student.aspx
~/Views/Shared/student.ascx
Here is my controller action:
[HttpPost]
public ActionResult SubmitStudent()
{
StudentViewModel model = TempData["model"] as StudentResponseViewModel;
ViewData["id"] = model.Id;
ViewData["name"] = model.Name;
string comment = Request["comment"];
var student = student.studentTable.Where(s => s.studentId == model.Id);
return View(student);
}
Here is my View:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<IEnumerable<string>>" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Student</title>
</head>
<body>
<div>
Student name listed below:
</div>
<table>
<% foreach (var item in Model) { %>
<tr>
<td>
<%= Html.Encode(item)%>
</td>
</tr>
<% } %>
</table>
</body>
</html>
A few things to consider here.
First of all, returning a view after a HTTP POST is really a bad design choiche. You can google about the PRG Pattern and you will find many articles that will explain why you should always redirect to a HTTP GET which will render your view.
Second, I find strange that your code is looking for a view name "student". As per MVC specification, the controller will look for a view named as the action method unless an overload of the View() method which accepts the view name as parameter is called (which is not your case, at least not in the code you posted).
In your example, it seems like it should look for a view named "SubmitStudent". Again, the model type you declare on your view doesn't match the model you're passing to it. It accepts an IEnumerable<string> but you're passing to it an IQueryable<Student> (that's what your student variable contains).
I think you omitted some parts of your code. The parts you posted don't quite match with one another.
In order for your code to work, you're going to need a view called SubmitStudent.aspx inside the Views\Student\ or Views\Shared\ folders.
It also looks odd that your view inherits a list of strings and not a Student object or whatever type of object your query returns. Your view is expecting an enumerable list of string's
This line is also confusing:
var student = student.studentTable.Where(s => s.studentId == model.Id);
Did you mean:
var student = model.studentTable.Where(s => s.studentId == model.Id);
Your view must be in "Views\Student\" - unless you have changed the view engine settings which I imagine you have not.
So I believe your view is not there.
I am writing my first MVC application, and struggling with how best to pass data from my controllers to my views. I have a very simple XML document structured like this. (Yes, this is Magic: The Gathering data)
<setlist>
<set>
<name>Alara Reborn</name>
<block>Shards of Alara</block>
<cards>145</cards>
<code>ARB</code>
</set>
<set>
<name>Archenemy</name>
<code>ARC</code>
</set>
</setlist>
(note that some of the nodes like "Block" and "Cards" are optional.)
On my first attempt, I was trying this:
' Load the set info
Dim doc As New System.Xml.XmlDocument
doc = LoadXML("setinfo.xml")
Dim listSet = doc.GetElementsByTagName("set")
ViewData("sets") = listSet
Then in my view, I was attempting to access the XmlNodeList like this:
<%
If ViewData("sets").count > 0 Then
For i = 1 To (ViewData("sets").count - 1)
%>
<tr>
<td><%= ViewData("sets")(i).SelectSingleNode("code").InnerText%></td>
<td><%= ViewData("sets")(i).SelectSingleNode("name").InnerText%></td>
<td><%= ViewData("sets")(i).SelectSingleNode("block").InnerText%></td>
</tr>
<%
Next
End If
%>
But I get an Object Block or With Block error when trying to access SelectSingleNode("block") on the second "set" node, since that node doesn't have a "block" node.
I also have a feeling that the way I'm approaching the view is all wrong. Is there a better way to get this simple XML data into the view so I can work with it?
You should consider creating a Set class (this will be the Model class in MVC) that the Controller loads the XML into. Then this Set class can then handle the absence of a 'block' element.
Binding your view directly to the serialised representation of your data is generally a bad idea. Even though this is just a first application it would be good to follow the 'rules' of the MVC pattern from the start, and you'll learn/understand more along the way!
A better way to pass your XML document to the view would be to create a class that represents your xml document, serialize your document into the type and then pass the instance of the class to the view.
One easy way to serialize your document into a class is to use the XmlSerializer.
Domain Class:
<System.Xml.Serialization.XmlRoot("setlist")> _
Public Class SetList
Inherits List(Of SetItem)
End Class
<System.Xml.Serialization.XmlType("set")> _
Public Class SetItem
<System.Xml.Serialization.XmlElement("name")> _
Public Name As String
<System.Xml.Serialization.XmlElement("block")> _
Public Block As String
<System.Xml.Serialization.XmlElement("cards")> _
Public Cards As String
<System.Xml.Serialization.XmlElement("code")> _
Public Code As String
End Class
Controller:
Public Class SetController
Inherits System.Web.Mvc.Controller
Function Index() As ActionResult
Using reader As System.IO.FileStream = System.IO.File.OpenRead("SetInfo.xml")
Dim xmlSerializer As New System.Xml.Serialization.XmlSerializer(GetType(SetList))
Dim setList As SetList = xmlSerializer.Deserialize(reader)
Return View(setList)
End Using
End Function
End Class
View (note this is a strongly typed view):
<%# Page Language="VB" Inherits="System.Web.Mvc.ViewPage(Of VB.SetList)" %>
<html>
<head>
<title>Test</title>
</head>
<body>
<div>
<table>
<tr>
<th>Code</th>
<th>Name</th>
<th>Block</th>
</tr>
<%For Each setItem In Model%>
<tr>
<td><%=setItem.Code%></td>
<td><%=setItem.Name%></td>
<td><%=setItem.Block%></td>
</tr>
<%Next%>
</table>
</div>
</body>
</html>