I've looked, tried several different solutions and haven't found anything that works (at least, not something with an example close enough to what I want for me to follow). I'm sure I'm missing something that would be a simple thing to a more experienced coder. Help?
I have a Model called Residents. It includes ResidentID, PFName, PLName. I have a controller for Residents. I have CRUD views for Residents. All working just fine.
I have a Model called Logs. It includes LogID, ResidentID, Comments. I have a controller for Logs. I have CRUD views for Logs. All working just fine.
I can display all the log entries for a Resident. Works fine. After a Log entry has been created, I can display the PFName using the method
#Html.DisplayFor(model => model.Resident.PFName)
Next, I want to Create a new log entry for a selected Resident.
That's where I'm having the problem. I would like the "Create" view (for the Log) to display the ResidentFName and ResidentLName of the selected resident, not the ResidentID.
A this point, from the Details view for a Resident, I have a CreateLog link.
#Html.ActionLink("New Log Entry", "../Log/Create", new { #ResidentID = Model.ResidentID})
This (likely not the best way) gives me a URL with the value of the selected ID
http://localhost:999/Log/Create?ResidentID=1
The value for the ResidentID is correct; it changes depending on which Resident is selected.
This value is correctly entered
#Html.TextBoxFor(model => model.ResidentID)
on the new CreateLog page using the Log Controller Create action.
public ActionResult Create(int ResidentID)
I plan to hide the ResidentID TextBox so the user doesn't see it. It seems I have to make it available in the form to be able create a new log entry.
The CreateLog form currently works as I have it now. I can create a log entry and verify that entry has been correctly recorded for the Resident.
But, I would like the form to display the PFName and PLName for the Resident so the user has visible feedback for which Resident was selected.
I believe that the related data (PFName and PLName) I want has to be passed to the CreateLog form .... somehow. I can't get it from the form.
Since there's only the unsaved entry for ResidentID, I can't use the value from the CreateLog form it to display related data. As mentioned, for the Lists, there is no such problem. It's only for CreateLog.
I've tried adding the data to the URL. Not working. I've tried setting the strings in the Controller (and the URL). Not working. I've looked at setting a cookie, but haven't ever done that so not sure what to set or where to put it or how to get the values from it. I've looked at setting a variable in the controller ... (have that working to display drop down lists, but a list to select from is not what I need -- I want the matching values from the related table).
Log.LogID(PK, Identity)
Log.ResidentID(FK)
Resident.PFName
Resident.PLName
I can directly create a view with these tables/fields in my SQLDB and update it.
Assuming a view model which looks something like this:
public class CreateLogViewModel
{
public int ResidentID { get; set; }
public string PFName { get; set; }
public string PLName { get; set; }
public string SomeLogCreationProperty { get; set; }
// other properties
}
Your controller could look something like this:
public ActionResult Create(int ResidentID)
{
var model = db.Residents.Where(r => r.ResidentID == ResidentID)
.Select(r => new CreateLogViewModel
{
ResidentID = r.ResidentID,
PFName = r.PFName,
PLName = r.PLName
// other properties
});
return View(model);
}
Then the view:
#model CreateLogViewModel
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.ResidentID)
#Html.HiddenFor(m => m.PFName)
#Html.HiddenFor(m => m.PLName)
#Html.EditorFor(m => m.SomeLogCreationProperty)
// other properties
<input type="submit" />
}
This would then POST back to:
[HttpPost]
public ActionResult Create(CreateLogViewModel model)
{
if (ModelState.IsValid)
{
return RedirectToAction("Index");
}
// Redisplay the form with errors
return View(model);
}
Expanding on John H and StuartLC answers, you need to use ViewModels and the following workflow:
Database->(load)->Model->Controller->(convert)->ViewModel->View
and
View->ViewModel->Controller->(convert)->Model->(save)->Database
So lets says you have the following models:
namespace Models
{
public class Residents
{
public int ResidentID { get; set; }
public string PFName { get; set; }
public string PLName { get; set; }
//...
}
public class Logs
{
public int LogID { get; set; }
public int ResidentID { get; set; }
public string Comments { get; set; }
//...
}
}
You need a ViewModel that combines the data you need for display and input in your Log\CreateView:
namespace ViewModels
{
public class ResidentLog
{
public int ResidentID { get; set; }
public string PFName { get; set; }
public string PLName { get; set; }
public string Comments { get; set; }
//...
}
}
Then inside the controller:
public class LogController : Controller
{
[HttpGet]
public ActionResult Create(int ResidentID)
{
// Run in debug and make sure the residentID is the right one
// and the resident exists in the database
var resident = database.Residents.Find(residentID);
var model = new ViewModels.ResidentLog
{
ResidentID = resident.ResidentID,
PFName = resident.PFName,
PLName = resident.PLName,
Comments = string.Empty,
// ...
};
// Run in debug and make sure model is not null and of type ResidentLog
// and has the PFName and PLName
return View(model);
}
[HttpPost]
public ActionResult Create(ViewModels.ResidentLog model)
{
if (!ModelState.IsValid)
return View(model);
var log = new Models.Logs
{
// Assumes LogID gets assigned by database?
ResidentID = model.ResidentID,
Comments = model.Comments,
};
// Run in debug and make sure log has all required fields to save
database.Logs.Add(log);
database.SaveChanges();
return RedirectToAction("Index"); // Or anywhere you want to redirect
}
}
Then your Log\CreateView:
#model ViewModels.ResidentLog
<!-- Display the values needed -->
<div>#Model.ResidentID - #Model.PFName - #Model.PLName</div>
#using (var form = Html.BeginForm(...))
{
<!-- This saves the values for the post, but in fact only ResidentID is actually used in the controller -->
#Html.HiddenFor(m => m.ResidentID)
#Html.HiddenFor(m => m.PFName)
#Html.HiddenFor(m => m.PLName)
#Html.EditorFor(m => m.Comments)
<input type="submit" />
}
You need to provide the additional information to the view.
This can be done in at least 2 ways
Use the ViewBag dynamic as a quick and dirty cheap and cheerful container to pass everything the view needs from the controller.
(preferred) Use a custom ViewModel with a tailor made class which holds everything the view needs. This is generally preferred as it is statically typed.
(I'm assuming that resident is already persisted in the database by the time the Log controller is called - you might need to fetch it elsewhere)
So, in your log controller, here's an example of using ViewBag:
[HttpGet]
public ActionResult Create(int residentID)
{
ViewBag.Resident = Db.Residents.Find(residentId);
return View();
}
You can then show the resident properties on the view by utilizing the ViewBag.
Edit
Yes, by persisted I meant in the Db - apologies about using unclear jargon.
Here's another example of ViewBag approach (the idea is to create a new Comment for another object):
Doing this the cheap + cheesy ViewModel way - in the HTTPGet Controller Create method:
public ActionResult Create(string objectType, int objectId)
{
// This is equivalent to youn fetching your resident and storing in ViewBag
ViewModel.Object = FetchSomeObject(objectType, objectId);
return View();
}
And in the View I use this (The ViewBag is accessible to Controller and View):
<title>#string.Format("Add new Comment for {0} {1}", ViewBag.Object.ObjectType, ViewBag.Object.Name);</title>
As you say, you will also need to do add a hidden for the ResidentId in your create log form
As per #JohnH's answer (+1), the BETTER way to do this (than using the magic ViewBag dynamic) is to create a custom ViewModel specifically for this screen. The ViewModel can either be reused both ways (GET: Controller => View and POST : Browser => Controller, or you even have separate ViewModels for the Get and Post legs.
With much thanks to all, I have it working. The final piece was telling the controller to return the model (nl). Here's the full spec for what's working:
I have created a ViewModel that includes
public class NewLog
{
public int ResidentID { get; set; }
public string PFName { get; set; }
public string PLName { get; set; }
public string Comment { get; set; }
// other properties
}
In the LogController,
public ActionResult Create(int ResidentID)
{
var resident = db.Residents.Find(ResidentID);
var nl = new NewLog
{
ResidentID = ResidentID,
PFName = resident.PFName,
PLName = resident.PLName,
Comment = string.Empty,
};
return View(nl);
}
In the Create.cshtml page,
#model My.Models.NewLog
The required ResidentID to be recorded with the new Log Entry
#Html.TextBoxFor(model => model.ResidentID, new {#Type = "Hidden"})
And the related, user-friendly display boxes for the person's name
#Html.DisplayFor(model => model.PFName)
#Html.DisplayFor(model => model.PLName)
And in the URL which is used to access the create page,
#Html.ActionLink("New Log Entry", "../Log/Create", new { #ResidentID = item.ResidentID, item.PFName, item.PLName})
Related
I have an a href link to a page which adds a parameter to the link for example:
tsw/register-your-interest?Course=979
What I am trying to do is to extract the value in Course i.e 979 and display it in the view. When attempting with the below code, I only return with 0 rather than the course value expected. ideally I'd like to avoid using routes.
Here is the view:
<div class="contact" data-component="components/checkout">
#using (Html.BeginUmbracoForm<CourseEnquiryPageSurfaceController>("PostCourseEnquiryForm", FormMethod.Post, new { id = "checkout__form" }))
{
//#Html.ValidationSummary(false)
#Model.Course;
}
And my controller:
public ActionResult CourseEnquiry(string Course)
{
var model = Mapper.Map<CourseEnquiryVM>(CurrentContent);
model.Course = Request.QueryString["Course"];
return model
}
This is the View Model:
public class CourseEnquiryVM : PageContentVM
{
public List<OfficeLocation> OfficeLocations { get; set; }
public string Test { get; set; }
public string Course { get; set; }
public List<Source> SourceTypes { get; set; }
}
SOLUTION:
After some research and comments I've adjusted the code to the below which now retrieves the value as expected
#Html.HiddenFor(m => m.Course, new { Value = #HttpContext.Current.Request.QueryString["Course"]});
Thanks all
Based on the form code you provided you need to use #Html.HiddenFor(m => m.Course) instead of just #Model.Course. #Model.Course just displays the value as text instead of building a input element that will be sent back to your controller.
If your problem is with a link prior to the view you referenced above, here's what I'd expect to work:
View with link:
#model CourseEnquiryVM
#Html.ActionLink("MyLink","CourseEnquiry","CourseController", new {course = #Model.Course}, null)
CourseController:
public ActionResult CourseEnquiry(string course)
{
// course should have a value at this point
}
In your view, you are only displaying the value of Course.. which isn't able to be submitted. You need to incorporate the value of course with a form input element (textbox, checkbox, textarea, hidden, etc.).
I would highly suggest using EditorFor or Textboxfor, but because your controller action is expecting just a string parameter you could just use Editor or TextBox.
#using (Html.BeginUmbracoForm<CourseEnquiryPageSurfaceController>("PostCourseEnquiryForm", FormMethod.Post, new { id = "checkout__form" }))
{
//#Html.ValidationSummary(false)
#Html.TextBox(Model.Course, null, new { #class = "form-control"});
<input type="submit" value="Submit" />
}
Then you should just be able to do this in your controller:
public ActionResult CourseEnquiry(string course) // parameter variables are camel-case
{
var model = Mapper.Map<CourseEnquiryVM>(CurrentContent);
if(!string.IsNullOrWhiteSpace(course))
model.Course = course;
return model;
}
Let me know if this helps.
I am very new to asp.net development. In my asp.net mvc project I have model "Employee" and I'm passing a list of "Employee" model to a RAZOR view and I'm trying to count different type of employees and show a summary.
my view is like this,
#{
int available = 0;
int onLeave = 0;
int away = 0;
int unAvailable = 0;
}
#foreach (var employee in Model){
<lable>#employee.Name</lable></br>
#if (#employee.Available){
#available=available+1;
}
#if (#employee.Unavailable){
#unAvailable=unAvailable;
}
#if (#employee.Away){
#away=away+1;
}
#if (#employee.Onleave){
#onLeave=onLeave+1;
}
}
<div>
<!--additional summary is displayed here-->
<label>Available:</label>#available
<label>Unavailable:</label>#unAvailable
<label>Away:</label>#away
<label>On Leave:</label>#onLeave
</div>
but when I run the my project variables "available","unAvailable","away" and "onLeave" don't get updated.
I'm sure that list is not empty because employee names are displaying.
can some explain me what is happening here and correct way of doing this
You should be doing this outside the before passing to the view like I mentioned in my original comment. You can create a new object called a ViewModel to represent the data exactly like you want it on the page. So I created a simple example, I only used the 4 properties of Employee you are displaying in you CSHTML page. On your View where you said your MODEL is either a list, arrary or whatever of Employee change it to EmployeeViewModel. Then in your controller where you get your list of employees set them to the Employees property of the Employee ViewModel.
public class EmployeeViewModel
{
public IEnumerable<Employee> Employees { get; set; }
public int TotalAvailable { get { return Employees.Count(emp => emp.Available); } }
public int TotalUnavailable { get { return Employees.Count(emp => emp.Unavilable); } }
public int TotalAway { get { return Employees.Count(emp => emp.Away); } }
public int TotalOnLeave { get { return Employees.Count(emp => emp.OnLeave); } }
}
public class Employee
{
public bool Available { get; set; }
public bool Unavilable { get; set; }
public bool Away { get; set; }
public bool OnLeave { get; set; }
}
//In the controller do this.
public ActionResult Index() //use your controller Action Name here
{
var employeeViewModel = new EmployeeViewModel { Employees = /*Your list of empoyees you had as a Model before here*/}
return View(employeeViewModel)
}
Change your CSHTML code to something like this:
#foreach(var employee in Model.Employees)
{
<label> #employee.Name </label></br>
}
<div>
<!--additional summary is displayed here-->
<label> Available:</label> #Model.TotalAvailable
<label> Unavailable:</label> #Model.TotalUnavailable
<label> Away:</label> #Model.TotalAway
<label> On Leave:</label> #Model.TotalOnLeave
</div>
An easy and quick way is:
<div>
<!--additional summary is displayed here-->
<label>Available:</label>#Model.Count(i => i.Available)<br>
<label>Unavailable:</label>do the same.
<label>Away:</label>do the same.
<label>On Leave:</label>do the same.
</div>
Make sure the model has already been "ToList()", or it might lead to mult-access of database.
Basically, I only use viewmodel when I need to pass more than 1 models to the view. Not worth in this case.
Make such calculations in View considered a BAD practice.
In your case better option will be create ViewModel with corresponding properties and then pass it to the model, previously calculating count for every type in controller using LINQ. Where you could reference your types like Model.available, Model.away and so on. Using ViewModel it is the best practice for MVC.
#Thorarins answer show you how to use LINQ in your code to calculate count for you types.
UPDATE:
You can use JS, but you should not, because it still not what supposed to happen in View. Work with data should not be handled in View. Don't be scared by ViewModels, they not that hard as it could seem. Please read this article which consider all ways to pass data to View, which has good example how create and pass ViewModel.
Mvc sample on how to do it:
you need a model class
public class EmployeeModel
{
public int Available {get; set;}
public int OnLeave {get; set;}
public int Away {get; set;}
public int UnAvailable {get; set;}
}
and a command:
public ActionResult Index()
{
var model = new EmployeeModel();
model.Available = employee.count(e=> e.available);
model.OnLeave = employee.count(e=> e.onLeave);
model.Away = employee.count(e=> e.away);
model.UnAvailable = employee.count(e=> e.unAvailable );
return View(model);
}
and a view
#model EmployeeModel
<div>
<!--additional summary is displayed here-->
<label>Available:</label>#Model.Available
<label>Unavailable:</label>#Model.UnAvailable
<label>Away:</label>#Model.Away
<label>On Leave:</label>#Model.OnLeave
</div>
I'm just picking up .net MVC and I've come across something that I can't work out. I'm obviously missing some basic principle but would love some help.
I have a ViewModel with two IEnumerables that I want to use to create dropdownlistfors. My GET works fine, the lists are populated as expected.
Now I'm posting the ViewModel back to a POST method, not to do anything useful but just to try and understand how mvc works. I expected that I would simply be able to re-populate the dropdownlistfors from the model that was posted back - but I get a null reference exception.
Other values, such as partyid, in the ViewModel survive the POST so i'm confused.
I can get it to work if I repopulate the lists but that seems wrong.
Can someone give me a pointer?
My ViewModel
public class DemoViewModel
{
//properties
public IEnumerable<tbl_server_lookup> servers { get; set; }
public int serverId { get; set; }
public IEnumerable<tbl_site_lookup> sites { get; set; }
public int siteId { get; set; }
public int partyid { get; set; }
public string message { get; set; }
public DemoViewModel()
{
}
}
My Controller
// GET: /Demos/Test/
[HttpGet]
public ActionResult Test()
{
DemoViewModel demo = new DemoViewModel();
using (var dbContext = new ADAPI.Models.db_ad_apiEntities2())
{
var serverList = dbContext.tbl_server_lookup.Where(s => s.server_name != null);
demo.servers = serverList.ToList();
var siteList = dbContext.tbl_site_lookup.Where(w => w.site_name != null);
demo.sites = siteList.ToList();
}
demo.message = "Enter the user id you would like to look up in the box below.";
return View(demo);
}
//
//POST: /Demos/Test/
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Test(DemoViewModel demo)
{
//It works if I uncomment this block...
/*using (var dbContext = new ADAPI.Models.db_ad_apiEntities2())
{
var myQuery = dbContext.tbl_server_lookup.Where(s => s.server_name != null);
demo.servers = myQuery.ToList();
var siteList = dbContext.tbl_site_lookup.Where(w => w.site_name != null);
demo.sites = siteList.ToList();
}*/
demo.message = "the user id you posted is: " + demo.partyid + ". The Server you selected is: ";// +demo.serverId;
return View(demo);
}
My View
#model ADAPI.ViewModels.DemoViewModel
<h2>Demos</h2>
<h3>#Model.message</h3>
#using (Html.BeginForm("Test","Demos"))
{
#Html.AntiForgeryToken()
<div class="">
<h4>Party ID</h4>
#Html.ValidationSummary(true)
<!-- input box for party id-->
#Html.TextBoxFor(model => model.partyid)
<!-- dropdown list of server types eg live vs test-->
#Html.DropDownListFor(model => model.serverId, new SelectList(Model.servers, "server_Id","server_name"))
#Html.DropDownListFor(model => model.siteId, new SelectList(Model.sites, "site_short_name","site_name"))
<input type="submit" value="Try" />
</div>
}
The Error
In MVC, model on the views are loaded in the controller action, they are not posted back along with the post action.
If you are used to ASPX's viewstate, there is no such thing in MVC, you need to load what you need for every view in the current action.
Dropdown lists are rendered into html as tag and returned to the server as plain single value.
You have to rebind/repopulate them on the server, wchich is annoying in scenarios like validation, where the same model should be returned to the client.
There is no support for that in the framework - you have to do it on your own.
One more thing - if you absolutely have to return the list items and want them back on the server, you can serialize tham and hide in some hidden field. But it's ugly and unsecure since anyone can change its value.
I declare a model with 4 string fields. 3 of which are read-only on the form:
public class HomeModel
{
[ReadOnly(true)]
[DisplayName("Service Version")]
public string ServiceVersion { get; set; }
[ReadOnly(true)]
[DisplayName("Session Id")]
public string SessionId { get; set; }
[ReadOnly(true)]
[DisplayName("Visiting from")]
public string Country { get; set; }
[DisplayName("Search")]
public string SearchString { get; set; }
}
I pass the model, after populating it, to my form:
[HttpGet]
public ActionResult Index()
{
var model = new HomeModel
{
Country = "Australia",
SearchString = "Enter a search",
ServiceVersion = "0.1",
SessionId = "76237623763726"
};
return View(model);
}
And the form is displayed as I expect:
<h2>Simple Lookup</h2>
#Html.LabelFor(m=>m.ServiceVersion): #Model.ServiceVersion<br/>
#Html.LabelFor(m=>m.SessionId): #Model.SessionId<br/>
#Html.LabelFor(m=>m.Country): #Model.Country<br/>
<p>
#using(Html.BeginForm())
{
#Html.LabelFor(m => m.SearchString)
#Html.TextBoxFor(m => m.SearchString)
<button type="submit" name="btnSearch">Search</button>
}
</p>
But, when I submit the form, and get the model back from the form, only the value of the SearchString is populated.
[HttpPost]
public ActionResult Index(HomeModel model)
{
return View(model);
}
Is it right that the other fields have been 'lost'? Does MVC not preserve the other members of the model class? And if this is expected - is there a way to re-get these? Or would I need to go back to my database, populate the model with the old values, and then use the new values from the form model?
It's possible the validity of wanting to read 'read-only' fields back from the model is questioned.. which is fair - but in the event that I find something suspect about the posted data, maybe I want to re-show the screen, and not have to re-read the data from a database again?
This is the correct behavior. Only the elements inside form will be posted to your action. Since it is posting the form so your fields should be inside the form in order to get them on your post method.
Update
Also, you cannot read particular field on your action method if you have taken that field readonly on your view. eg: displaying using #Html.LabelFor. In order to get field back on your action use #Html.HiddenFor if field is not to be edited.
I am working on a website that allows people to register for events. On the registration page, I have a form that captures the data, validates in an MVC controller, persists it between Views, and then is supposed to display a preview page.
Below is a simplified example of what I am trying to do.
Person ViewModel
public class Person{
public int TitleId {get; set;}
public SelectList Titles {get; set;}
}
Register Controller
public class RegisterController:Controller
{
[HttpGet]
public ActionResult Register(){
return View(new Person());
}
[HttpPost]
public ActionResult Register(Person person){
if(ModelState.IsValid){
TempData["Person"] = person
RepopulateSelectLists(); //Gets the data for the select list again
return RedirectToAction("Preview");
}
return View();
}
[HttpGet]
public Actionresult Preview(){
Person person = (Person)TempData["Person"];
}
}
Preview View
#Html.DisplayFor(m => m.TitleId); //Will display int, not selected value
Assuming my SelectList has all of the values, and I have the key/Id of the value selected on the previous page, how do I redisplay just the text of what they selected?
It doesn't work that way. In your Preview() page you'd take the given ID and find its associated name. You'll likely want to build a custom View Model (VM) in this case to keep the ID and the Name in the VM.
Also, the moment you hit F5 on the preview page, everything will be GONE. TempData doesn't persist and any refreshing of the page will lose the data.
I would recommend, instead, that on successful postback, you just return a different view based on the initial post data. For example:
[HttpPost]
public ActionResult Register(Person person){
if(ModelState.IsValid){
RepopulateSelectLists(); //Gets the data for the select list again
var previewVM = new PreviewVM();
// populate it with the model or whatever else you need for the preview
return View("Preview", previewVM);
}
return View();
}
An example VM of a person
public class Person { public int TitleId { get; set; } }
public class PersonVM : Person { public string TitleName { get; set; } }
// ... then in your Register method
var previewVM = new PersonVM { TitleId = person.TitleId, TitleName = GetTitle(person.TitleId) };
Assuming GetTitle does a lookup based on the TitleID. It's very coarse code, but I hope you get the gist of it. THen you'd use Html.DisplayFor(m => m.Title); And if your preview has a post to the next page, you'd want to have Html.HiddenFor(m => m.TitleId) to make sure the value passes on but you'd have to do that even with your current method. Alternatively you could store the Title in the ViewBag (or ViewData -- same thing) and simply not useDisplayForto display the value and pass in the value from theViewBag`
Notice I scrapped the "Preview" method completely. That way, if you hit F5 at this point, it just re-posts with the same data you used to get to the preview page. And assuming you're not saving (I don't see where you are) then there's no harm in posting back. Make sense?