MVC3 One View - Multiple PartialViews - One Model Per PartialView - asp.net-mvc

I'm new to the whole MVC 3 style coding. I'm running into an issue, but first here's how I have my website laid out.
_Layout.cshtml contains
#if (Request.IsAuthenticated)
{
<div class="span2">
#Html.Partial("_NavigationPartial")
</div>
<div class="span10">
#RenderBody()
</div>
}
else
{
#RenderBody()
}
#RenderBody will display my Profile.cshtml file that contains the following:
#{
ViewBag.Title = "My Profile";
}
<div class="row-fluid well">
<div class="page-header">
<h1>
Profile</h1>
</div>
#{
Html.RenderPartial("ChangePersonalInformationPartial");
Html.RenderPartial("ChangePasswordPartial");
}
</div>
As you see, I have two Partials (One to change the Personal Information, the other to Change the Password).
Each one of these Partials uses it's own Model (ChangePersonalInformationModel and ChangePasswordModel).
My problem comes when I click submit on my ChangePasswordPartial, it reloads the _Layout.cshtml page but this time only loads up ChangePasswordPartial.cshtml. I need it to load up Profile.cshtml. But, if I go ahead and change under my AccountController.cs the return View(); to return View("Profile"); I get an error saying:
The model item passed into the dictionary is of type
'PROJECT.Models.ChangePasswordModel', but this dictionary requires a
model item of type 'PROJECT.Models.ChangePersonalInformationModel'.
How can I fix this problem?
Thanks!

Basically you have to make a redirect to the profile action in the ChangePassword action once you saved the password information.
UPDATE:
First you should have a common model say ProfileModel that wraps up the ChangePasswordModel and ChangePersonalInformationModel.
So here are the actions that displays the profile information for viewing and editing.
// this action will returns a views that displays profile info
public ViewResult Profile(string username)
{
ProfileModel model = .. get the profile from database based on username
return View(model);
}
// this action will returns the profile info for editing or adding a new profile
public ViewResult EditProfile(string username)
{
.. if the profile already exists get from database
ProfileModel model =
.. if this is a new profile create an empty model
ProfileModel model = new ProfileModel();
model.ChangePasswordModel = new ChangePasswordModel();
model.ChangePersonalInformationModel = new ChangePersonalInformationModel();
return View(model);
}
Your EditProfile.cshtml will be like this
#model Models.ProfileModel
...
#{
Html.RenderPartial("ChangePersonalInformationPartial",
Model.ChangePersonalInformationModel);
Html.RenderPartial("ChangePasswordPartial", Model.ChangePasswordModel);
}
...
This will be your ChangePassword action
[HttpPost]
public ActionResult ChangePassword(ChangePasswordModel model)
{
if(ModelState.IsValid)
{
// save the ChangePasswordModel to database and display the profile info
// or even you can redirect to EditProfile for more editing
return RedirectToAction("Profile");
}
.. there are validation errors so get the complete profile model from database
.. the ChangePasswordModel form will be filled by the details entered in the form
.. and not from the db details this will be taken care by the framework itself.
ProfileModel model =
return View("EditProfile", model);
}

Related

Two strongly types partial views in one razor view

I am using Asp.Net identity logic module for authentication process. I am using this theme for login and signup and external logins all in one view.
Here is my Login.cshtml view that contain social login, register and login partials
#using Helping.ViewModels
#{
ViewBag.Title = "Log in";
}
<div class="container">
<div class="row">
<br />
<br />
<div class="col-lg-4">
<div>
<section id="socialLoginForm">
#Html.Partial("_ExternalLoginsListPartial", new ExternalLoginListViewModel { Action = "ExternalLogin", ReturnUrl = ViewBag.ReturnUrl })
</section>
</div>
</div>
<div class="col-lg-4">
<h2><b>Sign Up</b></h2>
<hr />
#Html.Partial("Register")
</div>
<div class="col-lg-4">
<h2><b>Log In</b></h2>
<hr />
#Html.Partial("LocalLogin")
</div>
</div>
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
LocalLogin and Register are the strongly typed partial views.Problem is that when I try to Login with a user that doesnot exist it returns the model , here is the action
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindAsync(model.UserName, model.Password);
if (user != null)
{
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account-Resend");
ViewBag.errorMessage = "You must have a confirmed email to log on.";
return View("Error");
}
else
{
await SignInAsync(user, model.RememberMe);
return RedirectToLocal(returnUrl);
}
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
It return following error
The model item passed into the dictionary is of type 'Helping.ViewModels.LoginViewModel', but this dictionary requires a model item of type 'Helping.ViewModels.RegisterViewModel'.
My Register view expects RegisterViewModel and My LocalLogin expects LoginViewModel
#using Helping.ViewModels
#model LoginViewModel
#model HelpingHands.ViewModels.RegisterViewModel
How to provide both the models with one view need help ?
You need to combine both your LoginViewModel and RegisterViewModel into one model (ViewModel).
Like..
public class LoginRegisterModel
{
public LoginViewModel LoginModel {get;set;}
public RegisterViewModel RegisterModel {get;set;}
}
Then you can pass this viewModel to your view..
#model Helping.ViewModels.LoginRegisterModel
Which says this view will be using the class LoginRegisterModel as the model..
And inside the view for your two partial views you can use..
Html.Partial("LocalLogin", Model.LoginModel)
Html.Partial("Register", Model.RegisterModel)
The error your getting is because you are not passing any model to your Register view and by default the model passed to your main view is carried forward to the call of partial Register view.
** I m on mobile, forgive me for bad formatting. And if anyone can format the code section its greatly appreciated**

Best practice for rendering data after posting a form in mvc 4.0

Example:
I have a 'Contact Us' view and controller.
My view renders a contact us form as well as the rest of the page containing postal, telephone and email information.
When the form is submitted I want to render the same data, just minus the contact us form and display a 'message sent' instead.
I have a 'Send' method on the controller and can create a 'Send' view with all the data from the contact us view, minus the contact us form and with the 'message sent' string. But obviously having the code now duplicated in two places is far from ideal.
Is there a better way to do this?
I would suggest you to use Ajax.BeginForm instead of using BeginForm. The reason is you don't need to create another action, Ajax.BeginForm will update the display partial view for you.
Below is an example:
Action
[HttpGet]
public ActionResult Contact()
{
return View(new Contact());
}
[HttpPost]
public ActionResult Contact(Contact contact)
{
if (ModelState.IsValid)
{
//
}
return PartialView("_messagePartialView", contact);
}
View
#model Demo.Models.Contact
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<div id="result">
#using (Ajax.BeginForm(new AjaxOptions { UpdateTargetId = "result" }))
{
#Html.EditorFor(x => x.Email)
<input type="submit" value="OK" />
}
</div>
Partial View: _messagePartialView
#model Demo.Models.Contact
<h1>
#Model.Email
</h1>
I will assume you have a Contact Model that you use to get the data from user.
And I assume you have the Send Method as follows:
[HttpPost]
public ActionResult Send(Contact contact){
// process your model. ie : send email etc.
TempData["contactData"] = contact;
return RedirectToAction("Sent");
}
public Actionresult Sent(){
return View();
}
In sent view you can use TempData and access Contact model properties.

ViewBag.Title vs. Page.Title in ASP.NET MVC 3

Why sometimes #ViewBag.Title contains the right page title while #Page.Title is null? We are debugging our View/Layout code and we noticed this difference.
Thanks.
When your are using asp.Net MVC you have a few tools you can use to get data to your page.
The model you send to the view.
The ViewBag.
TempData.
Cookies.
Session.
Each one of theese has their own use cases
The example we will be using is a basic
List and update View
For a collection of companies
The Model
The model Should be used whenever you have a defined dataset being sent to a view and should contain the primary data for the page and usally a model is specific to a page or controller.
The ViewBag
The ViewBag should be used whenever you need to send general data to a page that is not defined on the model or data that is used across your view tree.
Temp Data
Temp Data is as the name states storage for temporary data this should be used in cases where you are unsure if the data will reach its destination or when you want to pass data between actions without adding parameters.
Cookies
Cookies are like Global variables the host data across your entire application.
Session
Sessions like cookies are global variables but differ in their lifetime sessions can be used for things like Storing form data between requests dont use session to do this rather use something like http://garlicjs.org/
Lets look at the example
We have the following ViewModel
public class Company
{
public int? id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
The layout page looks like this
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
//This looks for the Title property on the ViewBag and inserts it in the title tags
<title>#ViewBag.Title</title>
</head>
<body>
#Html.Action("Header", "PageElements")
#RenderBody()
</body>
</html>
The Header Action on the PageElements Contoller looks like this
public PartialViewResult Header()
{
ViewBag.CurrentLink = HttpContext.Current.Request.Cookies["CurrentLink"];
return PartialView();
}
The Header Partial View looks like this
<header>
<ul>
<li class="#(ViewBag.CurrentLink == "Home" ? "Current" : "")">
#Html.ActionLink("Home", "Index", "Home")
</li>
<li class="#(ViewBag.CurrentLink == "Companies" ? "Current" : "")">
#Html.ActionLink("Companies", "Index", "Companies")
</li>
</ul>
</header>
The controller action for update looks like this
public ViewResult Update(int id)
{
//Gets a company from the database
var model = db.GetCompany(id);
//Sets The Title property on the ViewBag to Update : CompanyName
ViewBag.Title = String.Format("Update : {0}", model.Name);
//Sends the company to the view
return View(model);
}
The update view looks like this
#model Application.Models.Company
#{
Layout = "_layout.cshtml";
}
#using (Html.BeginForm())
{
#Html.HiddenFor(model => Model.id)
#Html.LabelFor(model => Model.Name)
#Html.TextBoxFor(model => Model.Name)
#Html.LabelFor(model => Model.Description)
#Html.TextAreaFor(model => Model.Description)
<button>Submit</button>
}
The Post Action for Update looks like this
[HttpPost]
public ActionResult Update(Company model)
{
//Attempts to update the model
if (model.Update())
{
//Set the message to update succeeded
TempData["Message"] = String.Format("{0} Successfully updated");
}
else
{
//Set the message to update failed
TempData["Message"] = String.Format("{0} filed to update");
}
return RedirectToAction("Index");
}
The Controller action for companies looks like this
public ViewResult Index()
{
//Index is the entrypoint for companies so we set the currentlink to companies while we are in companies
HttpContext.Current.Request.Cookies["CurrentLink"] = "Companies";
//Gets the success or failure message from the temp data
ViewBag.Message = TempData["Message"];
//Sets The Title property on the ViewBag to List of companies
ViewBag.Title = "List of companies";
var model = db.GetAllCompanies();
return View(model);
}
The view for the list looks like this
#model IEnumrable<Application.Models.Company>
#{
Layout = "_layout.cshtml";
}
<span>#ViewBag.Message</span>
<ul>
#foreach (var company in Model)
{
<li>#company.Name : #company.Description #Html.AnchorLink("Edit", "Update", new { company.id}) </li>
}
</ul>
Lets discuss how the flow of this application works
The Index Action gets triggered on companies.
We set the current link to companies in a Cookie
Then we set the Title in the ViewBag
We check the TempData for messages and put them into the ViewBag
Then we put the collection of companies into a Model and send it to the page.
Now razor kicks in and starts renders the page first
Then the layout renders and gets the Title value from the ViewBag
Then the header partial view renders and gets the currentlink value from a Cookie
We click the update button for a company
The update action runs
Gets the company from a database and sends it to the view in a Model
We change the title to update in the ViewBag
The page renders first and puts the company data on the form, from the Model
Then the layout renders and Gets the title value from the ViewBag
Then the header renders and gets the currentlink from a cookie (the value is still companies we did not have to change it)
Then we submit the form and add a message to the TempData
now we are back at the company list and we can get the success or failure message from the TempData
We used the Model to send Specific data to the view.
We used the ViewBag to send General data to the view and the layout.
We used TempData to pass data between actions without using parameters.
We used Cookies to Store data that wouldn't change for a while.
The correct is using ViewBag.Title, because it is on the context of your ViewBag property of your View while Page.Title is something that comes from asp.net webforms. That is the reason why Page.Title is null, there is no context for it.

MVC 3 StackOverflowException w/ #Html.Action()

I've looked over a bunch of other reports of this, but mine seems to be behaving a bit differently. I am returning PartialViewResults for my child actions, so that's not the source of the recursion. Here's a dumbed down version of what I have.
// The Controller
[ChildActionOnly]
public ActionResult _EditBillingInfo()
{
// Generate model
return PartialView(model);
}
[HttpPost]
public ActionResult _EditBillingInfo(EditBillingInfoViewModel model)
{
// Update billing informatoin
var profileModel = new EditProfileViewModel()
{
PartialToLoad = "_EditBillingInfo"
};
return View("EditProfile", profileModel);
}
[ChildActionOnly]
public ActionResult _EditUserInfo()
{
// Generate model
return PartialView(model);
}
[HttpPost]
public ActionResult _EditUserInfo(EditUserInfoViewModel model)
{
// Update user informatoin
var profileModel = new EditProfileViewModel()
{
PartialToLoad = "_EditUserInfo"
};
return View("EditProfile", profileModel);
}
public ActionResult EditProfile(EditProfileViewModel model)
{
if (String.IsNullOrEmpty(model.PartialToLoad))
{
model.PartialToLoad = "_EditUserInfo";
}
return View(model);
}
// EditProfile View
#model UPLEX.Web.ViewModels.EditProfileViewModel
#{
ViewBag.Title = "Edit Profile";
Layout = "~/Views/Shared/_LoggedInLayout.cshtml";
}
<div>
<h2>Edit Profile</h2>
<ul>
<li class="up one"><span>#Ajax.ActionLink("Account Information", "_EditUserInfo",
new AjaxOptions { UpdateTargetId = "EditProfileDiv", LoadingElementId = "LoadingImage" })</span></li>
<li class="up two"><span>#Ajax.ActionLink("Billing Information", "_EditBillingInfo",
new AjaxOptions { UpdateTargetId = "EditProfileDiv", LoadingElementId = "LoadingImage" })</span></li>
</ul>
<img alt="Loading Image" id="LoadingImage" style="display: none;" src="../../Content/Images/Misc/ajax-loader.gif" />
<div id="EditProfileDiv">
#Html.Action(Model.PartialToLoad)
</div>
</div>
The partial views are both forms for updating either the user information or billing information.
I debugged through this and found what is happening, but cannot figure out why. When a user browses to EditProfile, it load up with the _EditUserInfo partial and the form is there for editing. When you change some info and submit the form it hangs and you get a StackOverflowException in the EditProfile view on the call to #Html.Action(). What happens is on the initial visit to EditProfile, the #Html.Action calls the HttpGet version of _EditUserInfo. You make some changes to the user info and click submit. Once the information is updated the EditProfile view is returned again, but this time #Html.Action calls the HttpPost version of _EditUserInfo which updates the user information again, returns the EditProfile view again and the #Html.Action calls the HttpPost version of _EditUserInfo... You get where this is going. Why after form submission does it call the post version and not the get version like it did for the initial visit to EditProfile?
Thanks for any help!
I might be getting this a bit wrong, it's been a long day so, but in EditProfile you set PartialToLoad (if it's empty) to "_EditUserInfo", then in _EditUserInfo you set it again to _EditUserInfo, won't this create a loop that behaves as what you are experiencing?

ASP.NET MVC 3 Adding comments in article view

I have article model with public ICollection<Comment> Comments { get; set; } and comment model. I have created view for article (Details view) and I want to show everything from model article (not problem) and comments to article to and after comments then show form for adding comment to article (not in other page, I want it in the view with article). For now I have this:
#model SkMoravanSvitavka.Models.Article
#{
ViewBag.Title = "Zobrazit";
}
<h2>Zobrazit</h2>
<fieldset>
<legend>Article</legend>
<div class="display-label">Title</div>
<div class="display-field">#Model.Title</div>
<div class="display-label">Text</div>
<div class="display-field">#Model.Text</div>
<div class="display-label">PublishedDate</div>
<div class="display-field">#String.Format("{0:g}", Model.PublishedDate)</div>
</fieldset>
#if (Model.Comments != null)
{
foreach (var comment in Model.Comments)
{
#Html.Partial("_Comment", comment)
}
}
<p>
#Html.ActionLink("Edit", "Edit", new { id = Model.ArticleID }) |
#Html.ActionLink("Back to List", "Index")
</p>
It shows article and there is partial view for all comments to article. And now I am not sure how to add form for adding comments. Thank you
Edit: Here is my comment controller and create methods (vytvorit = create in czech :) ):
public ActionResult Vytvorit(int articleID)
{
var newComment = new Comment();
newComment.articleID = articleID; // this will be sent from the ArticleDetails View, hold on :).
newComment.Date = DateTime.Now;
return View(newComment);
}
[HttpPost]
public ActionResult Vytvorit(Comment commentEntity)
{
db.Comments.Add(commentEntity);
db.SaveChanges();
return RedirectToAction("Zobrazit", "Clanek", new { id = commentEntity.articleID });
}
When I change #Html.RenderAction to #Html.Action it works. It is showing textbox for comment and I can add comment but there is problem that it not just add textbox but it add my site again (not just partial view but all view) and I am sure I add Create view for comment as partial.
Create a new Partial view, make it a strongly typed one of type Comment.
from the scaffolding templates, choose "Create" template.
handle the normal add new scenario of the comment.
add this Partial view to the Article details page.
Note that when you are about to save a new comment, you will need to get the hosting Article ID.
Hope it's now clear, if not, let me know.
update: assuming that you will add the "AddComment" partial view to your "Article details" view, you can do the following in order to add the comment.
1- Modify the GET (Create) action inside your CommentController as the following:
public ActionResult Create(int articleID)
{
var newComment = new CommentEntity();
newComment.articleID = articleID; // this will be sent from the ArticleDetails View, hold on :).
return View(newComment);
}
1- make the POST (Create) action like this:
[HttpPost]
public ActionResult Create(Comment commentEntity)
{
// Complete the rest..
}
2- The Partial view for the comment will look something like this:
#model NGO.Models.Comment
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<div class="addcommentbox">
<h2> Add Comment </h2>
#Html.TextAreaFor(model => model.Description)
<div class="ErrorMessage">
#Html.ValidationMessageFor(model => model.Description)
</div>
<input id="addComment" type="button" onclick="" value="Add" />
</div>
}
3- inside the ArticleDetails page, in the desired place that you need the add comment section to show, use RenderAction method to render the AddComment Partial view like the following:
Html.RenderAction("Create", "Comment",new {articleID = Model.ID});
the previous line will call the GET(Create) action inside CommentColtroller and pass the current Article ID, so the AddComment Partial view will come already populated with the current Article ID (and this is what we want).
that's it, feel free to ask me if it's not clear yet, and do let me know if it worked for you
I strongly recommend you to create view model for your article page. like below one;
public class ArticleViewModel {
public Article _article {get;set;}
//this is for the comment submit section
public Comment _comment {get;set;}
//this for the comments that you will view
public IQueryable<Comment> _comment {get;set;}
}
after that, pass this to your view from your controller and make your view strongly-typed to this ArticleViewModel class.
Create a section which is wrapped inside form tag as below;
#using(Html.BeginForm()){
#*Put your code inside here.
Create inputs for sending comments. like below;*#
#Html.TextboxFor(Model._comment.Content)
}
and then create a method for that;
[HttpPost]
[ActionName("name_of_your_article_page_action")]
public ActionResult Create(Comment commentEntity) {
}
NOTE : Of course, you do not need to create a seperate viewmodel. you can hardcode the name your inputs but this makes it hard to use validation and other kind of things. but not impossible to use validation though !

Resources