my main (MyProfile) view contains links that when user clicks on the link the partial view loads in the div with existing data from DB that can be updated by the user.
#Ajax.ActionLink("Update 1", "Update1", new { email = #ViewBag.Email }, new AjaxOptions() { HttpMethod = "GET", UpdateTargetId = "divCurrentView", InsertionMode = InsertionMode.Replace })
#Ajax.ActionLink("Update 2", "Update2", new { email = #ViewBag.Email }, new AjaxOptions() { HttpMethod = "GET", UpdateTargetId = "divCurrentView", InsertionMode = InsertionMode.Replace })
<div id="divCurrentView">
</div>
Partial Views: example:
_Update1:
#model ViewModels.Update1
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.id)
#Html.LabelFor(model => model.Name)
#Html.TextBoxFor(model => model.Name)
<input type="submit" value="Update" />
}
_Update2:
#model ViewModels.Update2
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.id)
#Html.LabelFor(model => model.Website)
#Html.TextBoxFor(model => model.Website)
<input type="submit" value="Update" />
}
In the Controller:
public PartialViewResult Update1(string email)
{
var model = populate the viewmodel
return PartialView("_Update1",model);
}
public PartialViewResult Update2(string email)
{
var model = populate the viewmodel
return PartialView("_Update2",model);
}
It doesnt mean the user will click on all the links when accessing the main view.
I want to get feedback if my way is correct OR should I load all the data once when the user gets to MyProfile View and store the data in the session and when each partial view gets loaded data gets loaded from the session?
This would avoid calling the db every time partilaview gets loaded or is there a better approach?
Thanks,
UPDATE:
I tried to use Cache as suggested but the problem the data is store globally. If multiple users login and try to view/update the data, the data is identical for all of them Am I missing something?
This is what tried:
public PartialViewResult Update1(string email)
{
var cc = HttpRuntime.Cache.Get("cinfo");
Update1VM model = null;
if (cc == null)
{
model = populate the viewmodel
HttpRuntime.Cache.Insert("cinfo", model);
}
else
{
model = (Update1VM)cc;
}
return PartialView("_Update1", model);
}
Use Html.ActionLink in way you use is good solution. It makes controller simple and reuseble. You can easily add and remove views without changes in controller. It is easy to put them in diffrent view.
Generally it's more flexible solution.
If you afraid of calling the db every time you can always use some caching, but in your example it will query db only when user really need it and click it.
If you put this in one view it will be more complicated, more messy and less error prone.
Related
I have this page that contains 2 forms one exists in the layout file and the other in the view file. The first form is for newsletter subscription (an ajax form) and its location is common in the footer of the page, that's why it's in the layout and is rendered as a partial view. I have another view of the contact us page with its own form (normal form).
My issue is when I submit the contact us form, the code also goes into the action method of the subscription form and returns a model error with JsonResult causing the whole view to be rendered as text. I only want the action method of the contact us form to be executed.
Here is the subscription form in a partial view file
#model MyApp.Models.Subscriber
#using (Ajax.BeginForm("NewsletterSubscription", "Shared", null, new AjaxOptions
{
HttpMethod = "POST",
OnBegin = "OnBegin",
OnComplete = "OnComplete",
OnFailure = "OnFailure"
}, new { id = "subscribeForm" }))
{
#Html.AntiForgeryToken()
#Html.TextBoxFor(model => model.SubscriptionEmail)
#Html.ValidationMessageFor(model => model.SubscriptionEmail)
<input id="btnSubscribe" type="submit" value="Subscribe" />
}
And this is how it's rendered in the _layout.cshtml file
#{ Html.RenderAction("NewsletterSubscription", "Shared"); }
Here's the other form in contactus view file
#using (Html.BeginForm("Index", "Contact", FormMethod.Post, new { id = "contactForm" }))
{
#Html.AntiForgeryToken()
<div class="theForm">
<div class="theFormUnit">
<p>Fullname</p>
#Html.TextBoxFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="theFormUnit">
<p>Email</p>
#Html.TextBoxFor(model => model.Email)
#Html.ValidationMessageFor(model => model.Email)
</div>
<div class="theFormUnit">
<p>Phone</p>
#Html.TextBoxFor(model => model.Phone)
#Html.ValidationMessageFor(model => model.Phone)
</div>
<div class="theFormUnit">
<p>Message</p>
#Html.TextAreaFor(model => model.Message)
#Html.ValidationMessageFor(model => model.Message)
</div>
<input type="submit" value="Submit" />
</div>
}
When I debug the code, first the action method of the contact us is executed then the action method of the subscription and returns an error since the email was not provided.
The subscription action method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult NewsletterSubscription(Subscriber subscriber)
{
if (ModelState.IsValid)
{
}
else
{
return Json(new { success = false, message = "Failure Message" });
}
return Json(new { success = true, message = "Success Message"});
}
And contact us action method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(ContactViewModel contact)
{
if(ModelState.IsValid)
{
}
else
{
}
return View(contact);
}
I tried two solutions the first one partially solved the problem and the other solved it completely.
First solution was to add the following lines in the action method of the subscription form
if (!Request.IsAjaxRequest())
{
ModelState.Clear();
return PartialView("Partial/_NewsletterSubscription");
}
Here I am checking if the request is not an ajax request, which means it's the postback request for the contact us form, in this case I clear the model state to remove the error and return a new partial view. Although this solution solved the issue but I wasn't satisfied with it because I was not convinced with the fact that action method of the subscription form gets executed with the action method of the contact us form.
So later I thought of another simple solution, which totally solved the issue and the execution doesn't go into the action method of the subscription form when submitting the contact us form.
I simply changed the action method name from "NewsletterSubscription" to "Subscribe" so instead of
#using (Ajax.BeginForm("NewsletterSubscription", "Shared", null, new AjaxOptions
I changed it to
#using (Ajax.BeginForm("Subscribe", "Shared", null, new AjaxOptions
I'm trying to figure out the best way to create reusable forms with MVC. The situation is that I have a few forms used for editing groups of data that are used on several places on the site so obviously I dont want to copy paste the same forms for each of these places. I'm trying to use partial views but I cannot make it work completely and maybe I'm on the wrong track here.
The case which shows my problem is that I have one view with three different tabs (all tabs are in the same view and using simple javascript). On each tab I have added a partial view for editing that specific data. I have created the partial views with controller and everything. Each partial view contains its own form with validation summary.
If my controller actions simply return a PartialView the result is that the partial view is rendered alone, without the parent view surrounding it. Am I doing something wrong here if I get that result?
Am I on the wrong track to do this? Is there some other solution I should be doing instead?
To be more specific about the questions I have:
1) Is there a common pattern/solution for implementing components which include a form?
2) Using the solution here with partial views, how can I render the result of the controller action inside the master view the way it was rendered before the post?
Parts of the code for the master view:
<div class="tab-content no-border padding-24">
<div id="editProfile" class="tab-pane in active">
#{
Html.RenderAction("EditUserProfile", "UserData", new { userId = Model.Id });
}
</div>
<div id="password" class="tab-pane">
#{
Html.RenderAction("EditPassword", "UserData", new { userId = Model.Id });
}
</div>
</div>
The partial view for editing password:
#model Project.Web.Models.Shared.EditPasswordViewModel
<div id="summary">
#Html.ValidationSummary(false)
</div>
<div class="row">
#using (Html.BeginForm("EditPassword", "UserData", FormMethod.Post, new { id = "editpasswordform", #class = "form-horizontal site-validation" }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(o => o.Id)
#Html.PasswordFor(o => o.Password, new { #class = "col-xs-12 form-control", type = "password", id = "password-field" })
#Html.TextBoxFor(o => o.NewPassword, new { #class = "col-xs-12", type = "text", id = "new-password-field"})
#Html.TextBoxFor(o => o.RepeatNewPassword, new { #class = "col-xs-12", type = "password", id = "repeat-new-password-field"})
<button type="submit" class="width-35 pull-right btn btn-sm btn-primary">
<i class="icon-hdd"></i>
Save
</button>
}
</div>
Finally the controller method to save the password and go back to the same view:
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize()]
public PartialViewResult EditPassword(EditPasswordViewModel model)
{
try
{
if (ModelState.IsValid)
{
//Get the user
User user = GetUserById(model.Id);
//Update the user
UpdatePassword(user.Username, model.Password, model.NewPassword);
}
return PartialView("_EditPassword", model);
}
catch (DomainException ex)
{
ModelState.AddModelError("", ex.Message);
return PartialView("_EditPassword", model);
}
}
Thanks!
I have a scenario where I have a view with a partial that is loaded via $.get. The partial has the following code:
#model MvcApplication1.Models.CmaPartialModel
#using (Ajax.BeginForm("TestPost", new AjaxOptions { HttpMethod = "Post" }))
{
#Html.ValidationSummary()
for(var i = 0; i < Model.DataItemsWithLabels.Count; i++)
{
#Html.LabelFor(m => m.DataItemsWithLabels[i].DataName,Model.DataItemsWithLabels[i].DataName)
#Html.TextBoxFor(m => m.DataItemsWithLabels[i].DataValue)
#Html.ValidationMessageFor(m => m.DataItemsWithLabels[i].DataValue,"data value error")
#Html.TextBoxFor(m => m.DataItemsWithLabels[i].DataName)
#Html.ValidationMessageFor(m => m.DataItemsWithLabels[i].DataName,"data name error")
}
<input type="submit" value="Save" />
}
My controller action is:
[HttpPost]
public ActionResult TestPost(CmaPartialModel model)
{
if (ModelState.IsValid)
{
// code removed for quesiton
}
else
{
ModelState.AddModelError("E!", "Want to display this!");
}
return PartialView("Transaction", model);
}
Everything is working as expected in terms of client-side validation.
However, the errors that I have added in the controller are not displayed.
What am I doing wrong?
EDIT
I have altered the controller action to inlcude: ViewBag.Error = "error message";
And the partioal view to include #ViewBag.Error - This is not upating either. Is it perhaps an issue with AJAX?
The first parameter of AddModelError is important here. It is used to determine what part of your model the error applies to.
You specified E! which, given it contains something that isn't a valid C# identifier, probably doesn't map to any part of your model!
If you want to add a general error then use string.Empty as the first parameter. You should find that it displays in your validation summary.
Thaks to Dean for his help. Just wanted to make it clear that I was not using Ajax.BeginForm correctly. My view should have looked like this:
<div id="form">
#using (Ajax.BeginForm("TestPost", new AjaxOptions { HttpMethod = "Post", UpdateTargetId = "form" ))}
{
// form stuff here
}
<div>
The form was not updating with anything - not just errors as I had no UpdateTargetId
Can somebody help me on how to save and update data into multiple entities using a ViewModel?
I have a ViewModel that looks like this:
public class StudentViewModel
{
public Student student;
public StudentAddress studentAddress { get; set; }
public StudentPhoto studentPhoto { get; set; }
// Three entities are related to one to one relationship
public StudentViewModel()
{ }
}
My Controller is:
[HttpPost]
public ActionResult Create(StudentViewModel studentViewModel)
{
if (ModelState.IsValid)
{
return View(studentViewModel);
}
Student s = new Student()
{
Name =studentViewModel.Student.Name,
Speciality = studentViewModel.Student.Speciality,
DateOfReg = studentViewModel.Student.DateOfJoinig,
Qualification = studentViewModel.Student.Qualification,
Email = studentViewModel.Student.Email
};
StudentAddress sa = new StudentAddress()
{
StudentId= studentViewModel.Student.StudentId,
Address = studentViewModel.StudentAddress.Address,
Area = studentViewModell.StudentAddress.Area,
City = studentViewModel.StudentAddress.City,
State = studentViewModel.StudentAddress.State,
Mobile = studentViewModel.StudentAddress.Mobile
};
StudentPhoto sp = new StudentPhoto()
{
StudentId= studentViewModel.Student.StudentId,
Photo = studentViewModel.StudentPhoto.Photo
};
db.Students.Add(s);
db.StudentAddress.Add(sa);
db.StudentPhoto.Add(sp);
db.SaveChanges();
return RedirectToAction("Home");
}
View is:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Doctor</legend>
#Html.EditorFor(model => model.Student.Name )
#Html.EditorFor(model => model.Student.Speciality)
#Html.EditorFor(model => model.Student.DateOfJoinig)
#Html.EditorFor(model => model.Student.Standard)
#Html.HiddenFor(model => model.Student.StudentId)
#Html.EditorFor(model => model.StudentAddress.Address)
#Html.EditorFor(model => model.StudentAddress.Area)
#Html.EditorFor(model => model.StudentAddress.City)
#Html.EditorFor(model => model.StudentAddress.State)
#Html.HiddenFor(model => model.Student.StudentId)
#Html.EditorFor(model => model.StudentPhoto.Photo)
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
I was able to retrieve and display the data (from multiple entities) into the view. However, now I'm stuck on how can I save and update the above entities with the new data. Most of the examples are 1-1 relationship the mapping is automatic, but in this case the data belongs to multiple entities.
My problem is when i try to save data it redirected to the create page. "ModelState.IsValid" is false always so no data saved. Please help me how do i proceed.
Thanks.
This line at the top of your Action is wrong:
if (ModelState.IsValid)
{
return View(studentViewModel);
}
It should be the opposite, only if the Model is NOT valid, then you should stop the process and re-render the View with the form.
Try:
if (!ModelState.IsValid)
{
return View(studentViewModel);
}
In Controller you check if(modelstate.isvalid) - if is valid you returned view without saving data from view.
The problem with your implementation is that your view model contains a several models(Entities). This is not a good implementation.
Try to create a viewmodel which just contains the fields (flattened version) that you want to be edited by the user when creating a student. Use Data Annotations in your view model like Required or StringLength to validate user inputs.
#Html.ActionLink("Edit", "EditArticle", new { ArticleID = article.ArticleID })
I retrieved Article by ArticleID and return to edit page like this:
public ActionResult EditArticle(Guid ArticleID)
{
AddArticleModel AddArticleModel = new AddArticleModel();
AddArticleModel.Categories = entity.TBL_CATEGORIES.Select(a => a);
AddArticleModel.Article = dbo.SelectArticleById(ArticleID);
return View(AddArticleModel);
}
There is no problem until here.
And in my editing page I'm changing some attributes of article (not all attributes).For example I'm changing title, content, and updateddate. Like this:
#model DunyaYazilim.Models.AddArticleModel
#{
ViewBag.Title = "EditArticle";
Layout = "~/Views/Shared/_LayoutAuthor.cshtml";
}
#using (Html.BeginForm((string)ViewBag.FormAction, "Author"))
{
#Html.ValidationSummary(true, "Makale gönderilirken bir hata oluştu. Lütfen daha sonra tekrar deneyin.")
<div>
<div class="label_header">#Html.Label("Kategori Seçiniz:")</div>
<div>#Html.DropDownList("CategoryID", new SelectList(Model.Categories, "CategoryID", "Name"),Model.Article.TBL_CATEGORIES.Name)</div>
<div class="label_header">#Html.Label("Makale Başlık:")</div>
<div>#Html.TextBoxFor(m => m.Article.Title, new { #class = "my_textbox" })</div>
<div class="label_header">#Html.Label("Makale Açıklama:")</div>
<div>#Html.TextAreaFor(m => m.Article.Description, new { #class = "my_textarea" })</div>
<div class="label_header">#Html.Label("Makale İçerik:")</div>
<div>#Html.TextAreaFor(m => m.Article.ArticleContent, new { #class = "my_textarea" })</div>
<div><input type="submit" value="Gönder" class="my_button" /></div>
</div>
}
And then I post it to:
[HttpPost]
public ActionResult EditArticle(AddArticleModel AddArticleModel, String CategoryID)
{
//TODO: update database...
return View(AddArticleModel);
}
But unchanged attributes are return null(ArticleID, UserID, etc).So I cant Update the database, Because I dont have ArticleID after posting. What is the reason for this?
Thanks.
MVC doesn't maintain anything for you between requests. When you post to your action, it will post only the values that you have set up in your form. As you don't have your article id or user id in the form (or anywhere else, e.g. route or query string), MVC won't know about them during model binding for your EditArticle action.
If you want the extra details to be sent through with your post, you can put hidden fields in the form, e.g.
#Html.HiddenFor(m => m.Article.Id)