Creating reusable forms with partial views - asp.net-mvc

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!

Related

Unable to Post Dropdown Values Added at Runtime in ASP.NET MVC

For some reason I'm having trouble getting option values I add to an ASP.NET MVC dropdown at runtime to post.
In this case, I'm adding options to the dropdown at runtime using jquery.
This is what I have so far:
The Razor HTML:
#Html.DropDownListFor(sc => sc.SelectedComponents, Enumerable.Empty<SelectListItem>(), new { #class = "form-control", #id = "SelectedComponents", #name= "SelectedComponents", size = "5", multiple = "multiple" })
The relevant portion of the model:
public IEnumerable<string> SelectedComponents { get; set; }
Also, I can't get the posted values added at runtime to appear in the Forms Collection when I look in the controller action.
Can anyone suggest the best way to handle this particular situation?
Thanks much,
Pete
More information:
I'm trying to implement a simple solution using "BeginCollectionItem" to post added items at runtime when I submit the Razor Form.
In the main view I have this to display the partial view:
#foreach (var components in Model.selectedComponents)
{
#Html.Partial("SelectedComponents", Model)
}
The Partial View looks like this:
#model blah.blah.RequestViewModel
#using(Html.BeginCollectionItem("selectedComponents"))
{
<div class="form-group">
<label for="Component" class="control-label col-xs-12 col-sm-3">Selected Components:</label>
<div class="col-xs-12 col-sm-6">
#Html.DropDownListFor(model => model.selectedComponents, Enumerable.Empty<SelectListItem>(), new { #class = "form-control", #id = "SelectedComponents", #name = "SelectedComponents", size = "5", multiple = "multiple" })
</div>
<div class="col-xs-0 col-sm-3">
</div>
</div>
}
The relevant portion of the viewmodel looks like this:
public class RequestViewModel
{
public IEnumerable<string> selectedComponents { get; set; }
}
I simply want to post a list of strings that are added at runtime using jquery. This part is working and is not shown.
The controller that I'm posting to looks like this:
public ActionResult Create(HttpPostedFileBase[] files, RequestViewModel data)
{
}

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.

MVC Data Model from URL to Controller

Hi i need to pass some data from an URL to a controller in MVC. The data are in #Value = #Request.QueryString["rt"] in this code:
#using (Html.BeginForm("ResetPasswordToken", "Account")){
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "Reset Password non riuscito")
<div class="container above-footer login-form">
<div class="col-md-6" align="center" style=" margin-left:25%; margin-top:100px; margin-bottom:100px;">
<div class="editor-label">
#Html.LabelFor(m => m.ResetToken)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.ResetToken, new { #Value = #Request.QueryString["rt"] })
#Html.ValidationMessageFor(m => m.ResetToken)
</div>
And i need to retrieve this value in the AccountController when i go on the submit button associated to this view. What is the best mode to do this without see it in the page?
I know is a very simple question but i need to do only this modification for tomorrow and i am very busy. Thanks to all and sorry for the question
In the controller action that rendered this view (the GET action):
model.ResetToken = Request["rt"];
return View(model);
and then in the view simply:
#Html.TextBoxFor(m => m.ResetToken)
or if you don't want this to be shown in the form you could also use a hidden field:
#Html.HiddenFor(m => m.ResetToken)
Now when the form is submitted back to the ResetPasswordToken action you can read this value from the model:
[HttpPost]
public ActionResult ResetPasswordToken(MyViewModel model)
{
// you can use the model.ResetToken property here to read the value
}
Alternatively you could include this value as part of the url when generating the action attribute of your HTML form:
#using (Html.BeginForm("ResetPasswordToken", "Account", new { ResetToken = Request["rt"] }))
{
...
}

MVC4 load data into partial views

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.

Resources