MVC4 link between view and controller - asp.net-mvc

I'm writing my first MVC app (MVC4). A lot of it's been generated using the excellent Razor tools in VS2010, but now I've run into a wall and I feel I don't understand something fundamental.
In my app,
a User can have access to many Clients.
I have a Client Controller and View.
I have a User Controller and View (expanded version of the simple
membership's UserProfile, populated using User.Identity).
What I want to do is let the user edit a client's details and ALSO edit their own account details on the same page.
I've tried using Html.Partial to add the UserProfile view to the Client view, but it bombs out trying to pass the UserProfile view the Client model.
How do I make the Client view call the UserProfile controller so it can read the User.Identity and return the correct UserProfile view?
I tried making it a viewmodel with both the client and userprofile classes attached, and doing it all on the one screen, but it refused to send values to the controller on submit.
Client view:
#model MyProject.Client
<div>
#Html.Partial("_AcctDetailsPartial")
</div>
<div>
#using (Html.BeginForm())
{
.
.
.
#Html.HiddenFor(model => model.ClientID)
{
.
.
.
<client fields>
{
.
.
.
<div class="form-actions">
<button type="submit" name="ButtonName" value="Company" class="btn blue"><i class="icon-ok"></i> Save</button>
<button type="button" class="btn">Cancel</button>
</div>
}
</div>
UserProfile View:
#model Surecall.UserProfile
<div style="height: auto;" id="accordion1-1" class="accordion collapse">
#using (Html.BeginForm())
{
<h3 class="form-section">Personal Info</h3>
<label class="control-label">First Name</label>
#Html.TextBoxFor(m => m.FirstName, new { #class = "m-wrap span8", #id="FirstName" })
<label class="control-label">Last Name</label>
#Html.TextBoxFor(m => m.Surname, new { #class = "m-wrap span8" })
<label class="control-label">Phone Number</label>
#Html.TextBoxFor(m => m.Mobile, new { #class = "m-wrap span8" })
<label class="control-label">Email</label>
#Html.TextBoxFor(m => m.Email, new { #class = "m-wrap span8" })
<label class="control-label">Position</label>
#Html.TextBoxFor(m => m.Occupation, new { #class = "m-wrap span8" })
<label class="control-label">Interests</label>
#Html.TextBoxFor(m => m.Interests, new { #class = "m-wrap span8" })
<div class="form-actions">
<button type="submit" name="SaveLoginDetails" value="SaveUser" class="btn green"><i class="icon-ok"></i> Save</button>
<button type="button" class="btn">Cancel</button>
</div>
}
</div>

What you are looking for is a Viewmodel.
Right now, your controller serves back a view consisting of the "Client" class/model. But you want a view consisting of Client + UserProfile.
I am not considering unut of work, repository and similar patterns. What you first need is to create the viewmodel
public class ClientUserProfileVM()
{
public Client client {get; set; }
public UserProfile user { get; set; } //use actual Usermodel here
}
In your controller write something like this
public ActionResult GetClientAndUser()
{
ClientUserProfileVM viewmodel = here comes some LINQ magic
select new ClientUserProfileVM {
client,
user
};
return View(viewmodel);
}
This way you are giving a viewmodel to your view consiting of a client and a user, which you can access with #model.user or #model.client in your view
There are plenty of interseting links for this:
ViewModel Best Practices
What is ViewModel in MVC?
http://www.youtube.com/watch?v=AkptHlDSKSQ

Related

How to validate the HTML controls with data annotations in MVC?

In .Net MVC. I have a html control. Inorder to bind it with the model property I am using name attribute. How do we get the validations(using data annotation) provided in the model class property into the html control?
In Cshtml
#using (Html.BeginForm("ClaimWarranty", "WarrentyClaim", FormMethod.Post, new{ enctype = "multipart/form-data" }))
{
<div class="form-group row">
<label for="" class="col-md-2 col-form-label input-label">Email Address:</label>
<div class="col-md-8">
<input type="text" name="Emailaddress" class="form-control input-style" placeholder="example#company.com">
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" onclick="ValidateFileSize()" class="btn btn-default" />
</div>
</div>
}
//The model class is below;
public class ClaimWarranty
{
[Required(ErrorMessage = "Email ID is Required")]
[DataType(DataType.EmailAddress)]
[MaxLength(50)]
[RegularExpression(#"[a-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,4}", ErrorMessage = "Incorrect Email Format")]
public string Emailaddress { get; set; }
}
I am using the name property to bind the text box to the model property .
<input type="text" name="Emailaddress" class="form-control input-style" placeholder="example#company.com">
How do I get the validations in the html control ,provided in the model class (using the data annotations) as shown above without using jquery validations or razor code?
In View
#model Demo.Models.Student
#using (Html.BeginForm("SaveStudent", "Student", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="form-group">
#Html.LabelFor(model =>model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model =>model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model =>model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btnbtn-primary" />
</div>
</div>
}
In Model
public class Student
{
[Required(ErrorMessage = "Please enter name"), MaxLength(30)]
public string Name { get; set; }
}
By default, ASP.Net MVC framework executes validation logic during model binding. In Controller side, we need to check
if (ModelState.IsValid)
{
}
OR We can also check Individual validation, as shown below:
if (ModelState.IsValidField("LastName") == false)
if(!ModelState.IsValid)
{
// you can get the error information from model state, convert it into list
var validationErrors = ModelState.Values.Where(E => E.Errors.Count > 0)
.SelectMany(E => E.Errors)
.Select(E => E.ErrorMessage)
.ToList();
// now you have got the list of errors, you will need to pass it to view
// you can use view model, viewbag etc
ViewBag.ErrorList = validationErrors;
return View();
}
else
{
// perform your business operation, save the data to database
return View();
}
On View Page -
you have to add check for validation error list
if(ViewBag.ErrorList != null)
{
foreach(var errorMessage in ViewBag.ErrorList)
{
// here you can display the error message and format in html
}
}
Way you can display error on view page
1. #Html.ValidationSummary() - It will display summary of the validation errors
2. #Html.ValidationMessageFor(x => x.Emailaddress) - It will display error message
for specific property
3. you have to manually retrieve the error information from model state and then store it in list and pass to the view page.

Passing data from a View to a Controller in .NET MVC - "#model" not highlighting

The following code works as I need it to:
#using (Html.BeginForm("LOLOL", "PATIENT", null))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>PATIENT</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</fieldset>
<p>
<input type="submit" value="SUBMIT" />
</p>
}
In LOLOLController:
[HttpPost]
public IActionResult LOLOL(Patient p) {
var client = new MongoClient("mongodb://localhost:27017");
var userId = _userManager.GetUserId(HttpContext.User);
string db_name = "test" + userId;
var database = client.GetDatabase(db_name);
var collection = database.GetCollection<BsonDocument>("patients");
var filter = Builders<BsonDocument>.Filter.Eq("Name", p.Name.ToString());
var document = collection.Find(filter).First();
// I'm cutting short the rest of the code, because when I do something
// similar later, "collection.Find(filter).First()" fires an exception, I'll
// explain..
return View(p);
}
I have something equivalent to taking off the fieldset element in the HTML, leaving basically just a button in the "Html.BeginForm", but then the data is clearly not binding properly, which I know because if I just have a button and no data-entry, I click the button and then I get an error saying the data cannot be found from the database. (EDIT: I now have confirmed that this is indeed because the Patient object is not being passed to the controller quite as I expected it to, seems like a brand new Patient object was created upon calling html.beginform ... I thought that maybe the old Patient object was being passed so I did not have to enter all its data members every time we use Html.BeginForm)
In sum, I want to fill out a text box, click a button to load a new page and display the value of that textbox, but have that value also persisted in essentially a session state, so that if I call another Html.BeginForm function and go into a third view, the text from the first view will be displayed in the third view, even though I did not have to type its value in the second view. Hopefully I can repeat this process, and essentially load up the data members of a class with one view per data member.
Make sure you pass the data from the previous view to the new view from your Controller. When you pass it, include #HiddenFor for those properties from the previous view in your new view. That way the new view will keep and then pass the values to your next POST.
#Html.HiddenFor(model => model.PropertyYouPassedAndWantToKeepAndPassAgain
Edit: Here's the logic for using multiple views for one object... as requested.
Model:
public class Patient
{
string Name { get; set; }
string Address { get; set; }
string City { get; set; }
}
Page1 GET:
[HttpGet]
public ActionResult Page1()
{
Patient patient = new Patient();
return View("~/Views/Page1.cshtml", patient);
}
Page 1 View... only ask for the name.
#model mysite.Models.Patient
#using (Html.BeginForm("LOLOL", "PATIENT", null))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>PATIENT</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</fieldset>
<p>
<input type="submit" value="SUBMIT" />
</p>
}
Page1 POST... get the patient and pass it on to the next view...
[HttpPost]
public ActionResult Page1(Patient patient)
{
if (ModelState.IsValid)
{
return View("~/Views/Page2.cshtml", patient); // pass your patient to the second page view with the name
}
else
{
return View("~/Views/Page1.cshtml", patient);
}
}
Page2 GET... get the patient from the prior Page1 POST and send it off to the Page2 View.
[HttpGet]
public ActionResult Page2(Patient patient)
{
// Receive patient from Page1 post and pass it to new view... includes the name
return View("~/Views/Page2.cshtml", patient);
}
Page2 View gets the object... use a HiddenFor to keep the name which you just sent from the GET.
#model mysite.Models.Patient
#using (Html.BeginForm("LOLOL", "PATIENT", null))
{
#Html.HiddenFor(model => model.Name) #* This will keep the name on your next post *#
#Html.ValidationSummary(true)
<fieldset>
<legend>PATIENT</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Address)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Address)
#Html.ValidationMessageFor(model => model.Address)
</div>
</fieldset>
<p>
<input type="submit" value="SUBMIT" />
</p>
}
Since the HiddenFor holds the Name, it will be passed on your next post. It is there but hidden from the form itself.
[HttpPost]
public ActionResult Page2(Patient patient)
{
// Because of the HiddenFor, the Name will be passed because it was kept in the view... but hidden from the form itself.
// It's basically storing it for you to pass again
if (ModelState.IsValid)
{
// Pass object with Name and Address to next controller
return View("~/Views/Page3.cshtml", patient);
}
else
{
return View("~/Views/Page2.cshtml", patient);
}
}
Page2 POST
[HttpPost]
public ActionResult Page2(Patient patient)
{
// Because of the HiddenFor, the Name will be passed because it was kept in the view... but hidden from the form itself.
// It's basically storing it for you to pass again
if (ModelState.IsValid)
{
// Pass object with Name and Address to next controller
return View("~/Views/Page3.cshtml", patient);
}
else
{
return View("~/Views/Page2.cshtml", patient);
}
}
Page3 GET
[HttpGet]
public ActionResult Page3(Patient patient)
{
// Pass patient again... to your next view
return View("~/Views/Page3.cshtml", patient);
}
Page3 View...
#using (Html.BeginForm("LOLOL", "PATIENT", null))
{
#Html.HiddenFor(model => model.Name) #* Keep name again for your next post *#
#Html.HiddenFor(model => model.Address) #* Now we are keeping the address as well *#
#Html.ValidationSummary(true)
<fieldset>
<legend>PATIENT</legend>
<div class="editor-label">
#Html.LabelFor(model => model.City)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.City)
#Html.ValidationMessageFor(model => model.City)
</div>
</fieldset>
<p>
<input type="submit" value="SUBMIT" />
</p>
}
And so on and so forth... until you have your model complete and want to do something with it.

How do I get form values to controller

I'm working on our new startpage that will show a filter and a list of news posts. Each news post, and each newslist can be tagged with several business areas.
The newslist will contain news according to:
the settings of the newslist that can be altered by admins.
then filtered by the filter form on the page.
and, if the filter is empty but the user is logged in, filtered by the users preferences.
When the view IndexSecond is loaded, the filter gets its choosable business areas according to its newslist. My problem is that I don’t know how to get the selected business areas from the EditorFor to end up in the model.filteredBAs that is passed to IndexSecondFiltered?
When I come to a breakpoint in IndexSecondFiltered, the model is allways null.
In the view IndexSecond
#model Slussen.BLL.Models.PostFilterListModel
...
#using (Html.BeginForm("IndexSecondFiltered", "Home", new { model = model }))
{
#Html.HiddenFor(model => model.filteredBAs)
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<div class="col-sm-2">Business area</div>
<div class="col-md-10">
#Html.EditorFor(model => Model.newslistModel.BusinessAreas,
new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Go" class="btn btn-primary orange" />
</div>
</div>
}
In HomeController
public ActionResult IndexSecond()
{
//Known user?
int? uo = null;
if (User.Identity.IsAuthenticated)
uo = CurrentUser.UserOrganization.Id;
return View(
_queryDispatcher.Execute<PostFilterListModel>(
new GetFilteredNewsListById(1, uo, "", 1,
new System.Collections.Generic.List<int>(),
new System.Collections.Generic.List<int>())));
}
[HttpPost]
public ActionResult IndexSecondFiltered(PostFilterListModel model)
{
//Known user?
int? uo = null;
if (User.Identity.IsAuthenticated)
uo = CurrentUser.UserOrganization.Id;
return View(
_queryDispatcher.Execute<PostFilterListModel>(
new GetFilteredNewsListById(1, uo, "", 1,
new System.Collections.Generic.List<int>(), model.filteredBAs)));
}
I got help from a collegue.
I didn't need the [HttpPost] ActionResult IndexSecondFiltered at all.
When I replaced this
#using (Html.BeginForm("IndexSecondFiltered", "Home"), new { model = model })
with this
#using (Html.BeginForm("IndexSecond", "Home"))
the model was passed to the controller along with IsSelected-status

ASP.NET MVC - Determine if field has an error in Razor view

I'm working on an input form in ASP.NET MVC. My input form looks like this:
#using (Html.BeginForm("Result", "Contact", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { role="form" }))
{
<h4>What do you want to tell us?</h4>
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group label-floating">
<label class="control-label" for="Subject">Subject</label>
<input class="form-control" id="Subject" name="Subject" type="text">
</div>
<div class="form-group">
<input type="submit" value="Send" class="btn btn-primary btn-raised" />
</div>
#Html.AntiForgeryToken()
}
My model behind this form looks like this:
public class ContactModel
{
[Required(ErrorMessage="Please enter the subject.")]
[Display(Name="Subject")]
public string Subject { get; set; }
}
I want to conditionally apply classes and structure based on whether or not the Model is valid. I also want to do it per field. My question is, in Razor, how do determine if the "Subject" property is valid, or if it has errors? Thanks!
While ValidationMessageFor is the standard approach for displaying validation errors, the following code does exactly what you asked for and can be useful in rare circumstances:
#if (!ViewData.ModelState.IsValidField("Subject"))
{
//not valid condition
}
As was said in comments and by #PeterB - for displaying validation messages per-input should be used Html.ValidationMessageFor method somewhere near with your input on a view. But I want to notice: you have a model but do not use it in your form. More of this, you have data annotations for client and server validation and labeling and don't use them on your view too.
Please check this approach for your view:
#model /*your.possible.namespace.*/ContactModel
...
#using (Html.BeginForm("Result", "Contact", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { role="form" }))
{
<h4>What do you want to tell us?</h4>
<div class="form-group label-floating">
#Html.LabelFor(m => m.Subject, new { #class = "control-label" })
#Html.TextBoxFor(m => m.Subject, new { #class = "form-control", #id = "Subject" })
#Html.ValidationMessageFor(m => m.Subject)
</div>
}
This snippet should display the error message that you described in ErrorMessage property of Required data annotation after posting a form. You can find useful to enable an unobtrusive validation.
You can find an article about validation in ASP.NET MVC guides.
P.S.: And don't forget to perform a server-side validation in controller (for example).
In the Controller you can use ModelState.IsValid to see if there are errors, and also ModelState["nameOfField"] to see if a specific field has errors.
In the View you can use Html.ValidationSummary() (show all errors, if any) or Html.ValidationMessageFor() (show error for specific control, if any). Examples are plenty, both on & off StackOverflow.

asp.net mvc using url

There are VideoAlbum controller and Video controller. When I want to create a video, albumId comes from url.(http://localhost:38500/Admin/Video/Create?albumId=0)
In Creat(video) I am taking video name and some information. I want to post albumId with these information.
Here is my form:
#using (Html.BeginForm("Create", "Video", FormMethod.Post, new { #enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true)
<fieldset id="Proje_Bilgileri">
<div >
#Html.Label("Ad:")
</div>
<div >
#Html.EditorFor(model => model.Name)
</div>
<div >
#Html.Label("Kaynak:")
</div>
<div >
#Html.TextAreaFor(model => model.Data)
</div>
</fieldset>
<input class="button" id="formuGönder" type="submit" value="Save" />
}
Without seeing how you persist your videos to a database for example, it is hard to answer this question.
But if you are using a database, it should be your database that should be creating the new video id so you
do not need to pass it.
Instead your create action should be something similar to below:
[HttpPost]
public ActionResult Create(Video video)
{
if (ModelState.IsValid)
{
db.Videos.Add(video);
db.SaveChanges();
return RedirectToAction("Index");
}
}
You can see this in the music store tutorial

Resources