how to create an editable partial view in ASP.NET MVC - asp.net-mvc

I'm currently trying to create an edit view that consists of some user profile details and a list of roles that the user is assigned to. I thought that I could create an editable parent view that would contain all of the user profile textboxes and insert a partial view that would hold checkboxes for all of the roles.
However, I have found that when I try to update the textboxes in the partial view, those changes are not reflected in the parent model. Apparently, the partial view gets its own ViewData dictionary that is isolated from the parent's.
I was wondering if anyone had a solution that would allow me to pass changes made to a checkbox in a partial view back to the model of the parent. I have included some sample code below. Here is the parent view:
#model MvcWebsite.ViewModels.UserProfileEditViewModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>UserProfile</legend>
#Html.HiddenFor(model => model.UserId)
<div class="editor-label">
#Html.LabelFor(model => model.UserName)
</div>
... other properties of the user profile omitted for brevity
<div class="editor-field">
#Html.Partial("RoleCheckBoxEditTable", Model.Roles)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
We then have the child partial view:
#model List<MvcWebsite.ViewModels.RoleViewModel>
<table>
<tr>
#{
int count = 0;
foreach (var roleViewModel in Model)
{
if(count++ % 3 == 0)
{
#: </tr> <tr>
}
<td>
#Html.HiddenFor(r => roleViewModel.RoleName)
#Html.HiddenFor(r => roleViewModel.RoleId)
#Html.CheckBoxFor(r => roleViewModel.Assigned)
</td>
<td>
#Html.LabelFor(r => roleViewModel.Assigned, roleViewModel.RoleName)
</td>
}
}
</tr>
</table>
What I would like is to find a way to make both of these views update the same model, which could then be sent to the controller. Any help would be greatly appreciated.

I got around this problem by eliminating Partial Views entirely and replacing them with Display and Editor Templates.
Take the stuff from your View for that list of RoleViewModel, and create a new View under Views/Shared/EditorTemplates with the name RoleViewModel.cshtml (you might have to create the EditorTemplates directory first if you've never used it before).
On your top-level view, change Partial to EditorFor like this: #Html.EditorFor(model => model.Roles)
This approach works for me, and it preserves the baked in validation that I get by using the IValidatable interface on my Models.

Related

How do you create a MVC view for navigation properties (one to many, many to many)

I have two ASP.NET MVC EF entities sharing an association:
When I am editing or creating the User, I'd like to be able to attach one or more UserRoles. So, my User Create view looks like this:
#model MyModels.UserViewModel
#{ ViewBag.Title = "Create"; }
#using (Html.BeginForm())
{
<fieldset>
<legend>User</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Username)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Username)
#Html.ValidationMessageFor(model => model.Username)
</div>
<!-- how to create an editor to add/remove roles -->
<div class="editor-label">
#Html.LabelFor(model => model.UserRoles)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserRoles)
#Html.ValidationMessageFor(model => model.UserRoles)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Since Model.UserRoles is initially empty, I get nothing in this line #Html.EditorFor(model => model.UserRoles). I tried adding a drop-down where I can select existing roles using JavaScript (I guess retrieved as partial view?), but I don't know how to append a selected role to the fieldset so that it gets submitted.
How is this usually done in ASP.NET? I have been searching SO for a while but cannot find the appropriate solution.
To clarify, what I don't understand is how to add/append a new role to the client side, so that the submit button actually sends this inside the UserRoles list.
(edit) Ok, I've found this: How to add an item to a list in a ViewModel using Razor and .NET Core. It says that I should fetch a partial view which is written to look like the inputs which would be generated by #Html.EditorFor (with the next array index), and then append it using JavaScript.
That looks quite weird, is that really the correct approach?

Incorrect Razor syntax

I'm having some trouble understanding how #Razor works in a view. The code below is my view where a user can create a new post (I'm creating a forum)
What I want to do is remove the <Fieldset>
My problem is that I can't change the code I've marked.
#model Forum3.Models.Posts
<h2>CreatePost</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
//--- CAN'T EDITED ----
<fieldset>
<legend>Post</legend>
#*SET TopicID*#
#{
Html.HiddenFor(model => model.TopicId);
#Html.Hidden("TopicId",ViewData["currentTopicId"]);
}
//----END----
<div class="editor-label">
#Html.LabelFor(model => model.Text)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Text)
#Html.ValidationMessageFor(model => model.Text)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
If I remove the <Fieldset> and <Legend> I get this error on my HiddenFor code:
Parser Error Message: Unexpected "{" after "#" character. Once inside the body of a code block (#if {}, #{}, etc.) you do not need to use "#{" to switch to code.
If I then remove the #{...} to look like this:
#Html.HiddenFor(model => model.TopicId);
#Html.Hidden("TopicId",ViewData["currentTopicId"]);
I'll get an error when I click Create because the TopicId is not being set to my currentTopicId (This also happens if I leave the <fieldset> in)
I have no idea what going on here. Any ideas?
I'm not having any errors with that once you remove the surrounding block and use the # on both fields. However, one thing you do have that's probably messing up your post - you have both a Hidden and a HiddenFor for the same property. So if you look at the rendered markup, you'll see it there twice, so it gets posted twice (I'm not sure which one it assigns to the posted model).
The HiddenFor is all you need - just make sure your model contains the TopicId value, and you don't need it in ViewData at all, so you can get rid of that second one.

Client Side Validation for Hidden Input in ASP.NET MVC

I'm still a bit novice to the ASP.NET MVC architecture. I have an Edit page for data, which includes a hidden input. After testing my page, the 'Save' button wasn't doing anything and after some research learned it was a client-side validation issue.
After commenting the following line in the page:
#*#Html.HiddenFor(model => model.ID)*#
(where ID is a GUID), the page validated and posted.
From what I recall, the scaffolding put this code into my view. I just need to know how to fix this so that the ID field gets sent back properly to the controller and wanted to know why it wasn't validating.
Here is my View's code:
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<div class="object_basics">
#Html.HiddenFor(model => model.ID)
<div class="editor-label">
#Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
Have you tried to add the validation message for the ID ?
#Html.ValidationMessageFor(model => model.ID)
Client side validation for hidden fields not working, because jQuery validation ignore all hidden tags.
You must define HiddenRequiredValidator class to achieve your goal.
You can read a solution to solve this problem Here
In view, update from:
#using (Html.BeginForm())
To
#using (Html.BeginForm(new{ID = model.ID}))
Remove the hidden element [#Html.HiddenFor(model => model.ID)].
In your controller, update the action properties like below
public ActionResult YourActionName(string ID, string Title)
Hopefully, this answers your question.
Cheers,
Danny

Cannot get Editor Templates working in MVC3

I am converting a MVC2 app to MVC3.
I cannot seem to join up a View with an Editor Template.
So in my View the code is;
#model IEnumerable<ITOF.Models.LatestContractNumber>
#{
ViewBag.Title = "EditContractNumbers";
Layout = "~/Views/Shared/_Layout.cshtml";
}
using (Html.BeginForm())
{
<fieldset>
<legend>Edit Latest Contract Numbers</legend>
<div style="width:90%">
<div style="width:45%; float:left; vertical-align:top;">
<p>A contract number is in a format: AA/B999</p>
<p>Where AA is a 2 character code for the division, for example CD.</p>
<p>Where B is a letter for the contract, usually the first letter of the contract title.</p>
<p>999 is a 3 digit number.</p>
<p>The 999 number depends on the B letter. </p>
<p>Initially all the numbers start as 1 and are incremented each time the B letter is used.</p>
<p>This pages sets the initial 999 number for each B letter.</p>
<p>Once the application is up and running, this page should be used initially and then no more.</p>
#if (TempData["Message"] != null && TempData["Message"].ToString().Trim().Length > 0)
{
<p class="success">#TempData["Message"].ToString()</p>
}
</div>
<div style="width:45%; float:right; vertical-align:top;">
<div>
<table>
<tr>
<td>Letter</td>
<td>Number</td>
</tr>
#Html.EditorForModel(Model)
</table>
</div>
<div style="padding-top:20px">
<input type="submit" value="Submit Changes" />
</div>
</div>
</div>
</fieldset>
}
And my Editor Template is in the correct folder and now looks like;
#model ITOF.Models.LatestContractNumber
<tr>
<td>
#Html.HiddenFor(model => model.LatestContractNumberId)
#Html.DisplayFor(model => model.Letter)
</td>
<td>
#Html.TextBoxFor(model => model.Number, new { style = "width:30px" })
</td>
</tr>
The Model in #Html.EditorForModel(Model) relates to #model IEnumerable at the top of the page.
In the Editor Template, this should translate to a loop of ITOF.Models.LatestContractNumber, hence at the top of the template I have put #model ITOF.Models.LatestContractNumber
First, you are calling EditorForModel with a model of type IEnumerable<ITOF.Models.LatestContractNumber> (which is the Model for your View) but you should be calling it with ITOF.Models.LatestContractNumber (which is the Model your template expects to see/get). You need a foreach somewhere in there
foreach(ITOF.Models.LatestContractNumber number in Model) {
#Html.EditorForModel(number)
}
Second, not 100% sure, but EditorTemplates should be in Shared folder under the Views folder. So ~/Views/Shared/EditorTemplates/ and should be named after the class name so LatestContractNumber.cshtml.
HTH
The question turned out to be a red herring. The real problem was that having migrated to MVC and started prefixing partial views with _, the name of the Editor Template was invalid.
Thanks to Russ Cam for asking the right questions that got me the answer.

How to render an HTML attribute from a Razor view

I would like to use the same partial view for create, edit, and details views, to avoid duplicating the fieldset structure for an entity. Then, depending on which view renders the partial view, I would like to add a class of "read-only" to a div surrounding my fieldset and handle making the actual input fields read-only on the client, using css or jQuery, or whatever. How can I specify from my Razor view that I need this class added to the "item-details" div?
<div class="item-details">
<fieldset>
<legend>Product Details</legend>
#Html.HiddenFor(model => model.DetailItem.ProductId)
<div class="editor-label">
#Html.LabelFor(model => model.DetailItem.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DetailItem.Name)
#Html.ValidationMessageFor(model => model.DetailItem.Name)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
</div>
I would generally have a variable on the model that would indicate if your data is editable, and use it like:
<div class="item-details #(Model.IsReadOnly ? "read-only" : "")">
If you don't have it on the model however, you can use the ViewBag. In your parent view:
#{
ViewBag.IsModelReadOnly = true;
}
In your partial view:
<div class="item-details #(ViewBag.IsModelReadOnly ? "read-only" : "")">
Your html output for a readonly view would be
<div class="item-details read-only">
You can then access the div via jQuery and do what you need: $(".read-only")...
Why not just use
#Html.DisplayFor(model => model.[whatEver Property you are trying to show])

Resources