ASP.NET Razor ViewModel property is modified somewhere - asp.net

I have an extremely simple Controller + View
public ActionResult Edit(string username)
{
return View(ComponentFactory.GetAdapter<IUserListAdapter>().Get(username));
}
and
#model BAP.Models.UserList
#using GrmanIT.Utils.Web
#using BAP.Models
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Globale Benutzer</legend>
<div class="editor-label">
#Html.LabelFor(model => model.UserId)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserId)
#Html.ValidationMessageFor(model => model.UserId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.UserName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserName)
#Model.UserName
#Html.ValidationMessageFor(model => model.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Bundesland)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.Bundesland, new SelectList((IEnumerable<BAP.Models.Bundesland>)ViewData["BundeslandList"], "Value", "Text"))
</div>
<div>
<input type="submit" value="#LocalizationHelper.LocalizedLiteral("Save").ToString()" />
</div>
</fieldset>
}
<div>
#Html.ActionLink(LocalizationHelper.LocalizedLiteral("BackToList").ToString(), "Index")
</div>
#Model.UserName
this is by far the simplest controller and view we have in our MVC4 application, BUT - it does something weird:
I get the TextBox, which is created with #Html.EditorFor(model => model.UserName) prefilled with the UserId of the model instead of the UserName
I debugged it and it and there as always the correct value in UserName and UserId. You can also see, that I added #Model.UserName twice within the View to see if it get also correctly rendered, and yes, it prints the UserName and not the ID.
I've also checked references to the UserName-property and didn't find any, which would modify it. My question is - do you have any idea, where the code could be modified or how could if find it out?
It happens only on this one controller on this one action (out of ~25 controllers and ~200 actions)
Thank you

Ok, it was AGAIN the ModelState - as you can see in the code above - the parameter to the function is called "username" but it's actually the userId. And since there is already the parameter username, it is stored in the ModelState and when I call
#Html.EditorFor(model => model.UserName)
it takes the value from the ModelState (where actually the UserId is stored under the name of the action-parameter)
So the solution would be, either to call ModelState.Clear() or rather, rename the parameter to represent the actual value.

Related

How to use partial views from different models in ASP.NET MVC 5?

Hi I am trying to code a simple blog with using ASP.NET MVC 5 Framework. I have done CRUD operations of Posts. I mean I can add new articles and manage them. When I wanted to add comments to articles, I stuck. Comments will be added to Details pages of articles. So I should add Create comment page to Details page.
I used Code First model and I have two models. Articles and Comments. I decided to use partial views to enter comments. But result is an error:
The model item passed into the dictionary is of type 'System.Data.Entity.DynamicProxies.Article but this dictionary requires a model item of type 'Blog.Models.Comment'. Comments can not be added. I created 2 PartialViews, one of them is _CreateComments PartialView and other one is _Index PartialView
Details View:
#model Blog.Models.Article
#{
ViewBag.Title = "Details";
}
<h2>Details</h2>
<div>
<h4>Article</h4>
<hr />
<dl class="dl-horizontal">
<dt>
#Html.DisplayNameFor(model => model.Title)
</dt>
<dd>
#Html.DisplayFor(model => model.Title)
</dd>
<dt>
#Html.DisplayNameFor(model => model.Author)
</dt>
<dd>
#Html.DisplayFor(model => model.Author)
</dd>
<dt>
#Html.DisplayNameFor(model => model.Date)
</dt>
<dd>
#Html.DisplayFor(model => model.Date)
</dd>
<dt>
#Html.DisplayNameFor(model => model.ArticleContent)
</dt>
<dd>
#Html.DisplayFor(model => model.ArticleContent)
</dd>
</dl>
</div>
<div class="jumbotron">
#Html.Partial("~/Views/Comments/_CreateComments.cshtml", new Blog.Models.Comment())
#Html.Partial("~/Views/Comments/_Index.cshtml", new List<Blog.Models.Comment> { new Blog.Models.Comment() })
</div>
_CreateComment PartialView
#model Blog.Models.Comment
#using (Html.BeginForm("Create"))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Comment</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Date, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Date, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Date, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.CommentContent, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.CommentContent, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CommentContent, "", 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="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
_Index PartialView
#model IEnumerable
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Date)
</th>
<th>
#Html.DisplayNameFor(model => model.CommentContent)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Date)
</td>
<td>
#Html.DisplayFor(modelItem => item.CommentContent)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.CommentId }) |
#Html.ActionLink("Details", "Details", new { id=item.CommentId }) |
#Html.ActionLink("Delete", "Delete", new { id=item.CommentId })
</td>
</tr>
}
</table>
You have to use a ViewModel for that purpose. In POST, it will get the needed information. And in GET you will send the needed information.
For example: in GET you will send the article and needed areas. But in POST you will get the comment and commenter's name and date etc.
There are numerous issues with your code.
#using (Html.BeginForm()) means it will post back to the
Details() method (assuming that's the action which generated the
view), so it would need to be #using (Html.BeginForm("Create"))
The controls your generating will have name attributes such as
name="Item2.Date" which have no relationship to your Comment
class (Comment does not have a property named Item which is a
complex object with a property named Date)
The default value of a DateTime property is 1.1.0001 00:00:00
meaning that you have not initialized the value (e.g. Date =
DateTime.Today;) but its not clear why you would need the user to
enter a date anyway - surely that would be set to today's date in
the controller immediately before you save the comment.
You have not indicated what scripts
#Scripts.Render("~/bundles/jqueryval") is generating, but assuming
you using the jQueryUI datepicker, you will need at least
jquery-{version}.js and jquery-ui-{version}.js (plus jquery.validate.js and jquery.validate.unobtrusive.js for validation)
You have a table in your form which will not display anything other
that the initial values of a new Comment (and exactly the same as
is being displayed in the textboxes) so it seems a bit pointless
Note also your view does not display any existing comments for an article which seems unusual.
There a numerous ways so solve this including ajax so the user could stay on the same page and continue to add more comments, but based on what appears to be your current UI, then the model in Details view should be just Article and use a partial to render a form for a new Comment
Controller
public ActionResult Details(int? id)
{
....
Articles article = db.Articles.Find(id);
....
return View(article);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Comment model)
{
if (ModelState.IsValid)
{
model.Date = DateTime.Today; // set date before saving
db.Comments.Add(model);
db.SaveChanges();
return RedirectToAction("Index");
}
....
}
View
#model yourAssembly.Article
// Render properties of Article
....
// Add form for creating a new comment
#Html.Partial("_Comment", new yourAssembly.Comment)
// Add required scripts including jquery ui
and the partial view (_Comment.cshtml)
#model yourAssembly.Comment
#using (Html.BeginForm("Create"))
{
#Html.LabelFor(m => m.Content, new { #class = "control-label col-md-2" })
#Html.TextBoxFor(m=> .m.Content, new { #class = "form-control" } })
#Html.ValidationMessageFor(m=> m.Content, new { #class = "text-danger" })
<input type="submit" value="Create" class="btn btn-default" />
}
Side notes:
Do not use Tupple in your views, especially for a view that
involves editing since it generates name attributes which have no
relationship to your model. Always use view models.
You do not generate a control for the CommentId property so there
is no point including it in you [Bind] attribute (and in fact
means someone could post back a value and result in your code
throwing an exception.
Using <dl>, <dt> and <dd> tags are not appropriate in your
view (they are for A description list, with terms and
descriptions). Use <div> and <span> elements.

Hide / View Edit view form

I'm working on a small ASP.NET MVC 4 application in combination with MongoDB. Currently I have 4 views: Index, Create, List, Edit. Create is a form to put data in the database. List is a list to display the data. Edit is a form to edit the data. These three views are rendered inside the Index view (RenderAction).
The goal is to display only two views inside the index view. A combination of Index with Create, or a combination of Index with Edit.
At this moment I'm having problems with the Edit View (inside the controller):
[HttpGet]
public ActionResult Edit(string id)
{
Car car = CarRentalContext.Cars.FindOneById(new ObjectId(id));
return View(car.ConvertToUpdateViewModel());
}
Edit view:
#model MvcApplication1.ViewModels.UpdateCarViewModel
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>UpdateCarViewModel</legend>
#Html.HiddenFor(model => model.Id)
<div class="editor-label">
#Html.LabelFor(model => model.Make)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Make)
#Html.ValidationMessageFor(model => model.Make)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.NumberOfDoors)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.NumberOfDoors)
#Html.ValidationMessageFor(model => model.NumberOfDoors)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DailyRentalFee)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DailyRentalFee)
#Html.ValidationMessageFor(model => model.DailyRentalFee)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
Index View:
#model MvcApplication1.ViewModels.InsertCarViewModel
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
#{Html.RenderAction("Create", Model);}
</div>
<div>
#{Html.RenderAction("List", Model);}
</div>
<div>
#{Html.RenderAction("Edit", Model);}
</div>
</body>
</html>
Obviously the Edit view needs an ID to display, and it gets an error now when I use RenderAction, because there is no ID when I start the application. I want to hide this view when it is not needed, and only display this view when it is needed. How can I achive this without Javascript / Jquery.
Do I need an if/else statement inside my ActionResult?
The simplest and quickest thing to do would be to just check if id has a value
[HttpGet]
public ActionResult Edit(string id)
{
if (String.IsNullOrEmpty(id))
{
return null;
}
Car car = CarRentalContext.Cars.FindOneById(new ObjectId(id));
return View(car.ConvertToUpdateViewModel());
}
That's not a problem, in MVC Edit Controller typically has an id param, so to eliminate your problem you can just check if id is existing, something like this:
[HttpGet]
public ActionResult Edit(string id)
{
Car car = CarRentalContext.Cars.FindOneById(new ObjectId(id));
if (car != null)
{
return View(car.ConvertToUpdateViewModel());
}
//if we get this far show other view
return View();
}

Model Binding MVC3 Razor, How do I change input name?

I have setup a basic model binder by passing in a list to a view and running:
Controller:
[Authorize]
public ActionResult AddTracks(int id)
{
TrackRepository trackRepository = new TrackRepository();
//ShowTrackAssociationHelper showTrack = new ShowTrackAssociationHelper();
//showTrack.tracks = trackRepository.GetAssociatedTracks(id).ToList();
//showTrack.show = showRepository.GetShow(id);
TracksViewModel tracksModel = new TracksViewModel();
tracksModel.Tracks = trackRepository.GetAssociatedTracks(id);
ViewBag.ShowID = id;
return View(tracksModel);
}
View:
#model BluesNetwork.Models.TracksViewModel
#Html.EditorFor(model => model.Tracks, "TrackEditor")
TracksView Model:
public class TracksViewModel
{
public IEnumerable<Track> Tracks { get; set; }
}
TackEditor:
#model BluesNetwork.Models.Track
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.TrackID)
#Html.HiddenFor(model => model.ShowID)
<div class="editor-label">
#Html.LabelFor(x => x.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.TrackNumber)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.TrackNumber, new { maxlength = 2 })
#Html.ValidationMessageFor(model => model.TrackNumber)
</div>
#Html.HiddenFor(model => model.HQFileID)
#Html.HiddenFor(model => model.LQFileID)
<div class="editor-label">
#Html.LabelFor(model => model.InternalRating)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.InternalRating)
#Html.ValidationMessageFor(model => model.InternalRating)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.License)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.License)
#Html.ValidationMessageFor(model => model.License)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LicenseNumber)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LicenseNumber)
#Html.ValidationMessageFor(model => model.LicenseNumber)
</div>
<input type="submit" value="Save" />
}
At first I was getting:
Which gives me output as such on each input:
name="[0].ShowID"
however I wanted it to be:
name="track[0].ShowID"
I've seen examples/tutorials that show output like this but they don't go into detail about it.
After following RPM1984's advice and making the changes I got the error:
The model item passed into the dictionary is of type 'System.Data.Objects.ObjectQuery`1[BluesNetwork.Models.Track]', but this dictionary requires a model item of type 'BluesNetwork.Models.Track'.
Thank you in advance for all help
At
Not sure what you mean by "I have setup a basic model binder". That doesn't look like a model binder, that looks like a template or partial view.
Anyway, moving on....
You should have a ViewModel like this:
public class TracksViewModel
{
public IEnumerable<Track> Tracks { get; set; }
}
Main View:
#model TracksViewModel
#Html.EditorFor(model => model.Tracks)
Editor Template:
#model Track
#Html.EditorFor(model => model.ShowId)
No loops, no magic strings. Nice.
Which will render HTML like this:
<input type="text" name="tracks[0].ShowId" />
<input type="text" name="tracks[1].ShowId" />
<input type="text" name="tracks[2].ShowId" />
Which is what you want, right?
Basically if you want to change the names you will need to bust out some JavaScript or roll your own partial view.
Note that changing id's and names of bound fields is, generally speaking, a poor idea because this will break the baked in binding of your ViewModel. Do you really need to change the name?

Text box default value in Razor syntax

<div class="editor-label">
#Html.LabelFor(model => model.UserName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AccountModel.UserName)
#Html.ValidationMessageFor(model => model.AccountModel.UserName)
</div>
On this page load (View load), I would like the Username text box to have its value automatically populated from the standard aspnet membership informtion. How can assign this default value to the text box. Please help. thank you
In your controller action you could populate the view model and set the corresponding properties on it. So assuming your view is strongly typed to MyViewModel:
[Authorize]
public ActionResult Foo()
{
var model = new MyViewModel
{
UserName = User.Identity.Name
};
return View(model);
}
and in the view simply:
<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>
If your view model has some AccountModel property, you will have to instantiate and populate it in the controller action. In my example I have flattened the view model.

Create Views for object properties in model in MVC 3 application?

I have an Asp.Net MVC 3 application with a database "Consultants", accessed by EF. Now, the Consultant table in the db has a one-to-many relationship to several other tables for CV type information (work experience, etc). So a user should be able to fill in their name etc once, but should be able to add a number of "work experiences", and so on.
But these foreign key tables are complex objects in the model, and when creating the Create View I only get the simple properties as editor fields. How do I go about designing the View or Views so that the complex objects can be filled in as well? I picture a View in my mind where the simple properties are simple fields, and then some sort of control where you can click "add work experience", and as many as needed would be added. But how would I do that and still utilize the model binding? In fact, I don't know how to go about it at all. (BTW, Program and Language stand for things like software experience in general, and natural language competence, not programming languages, in case you're wondering about the relationships there).
Any ideas greatly appreciated!
Here's the Create View created by the add View command by default:
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Consultant</legend>
<div class="editor-label">
#Html.LabelFor(model => model.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
<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>
<div class="editor-label">
#Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Description)
#Html.ValidationMessageFor(model => model.Description)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
And here's the EF database diagram:
Update:
According to suggestion, I tried Steven Sanderson's blog solution, but I can't get it to work properly. I added this in the main view:
<div id="editorRows">
#foreach (var item in Model.Programs)
{
Html.RenderPartial("ProgramEditorRow", item);
}
</div>
<input type="button" value="Add program" id="addItem" />
I also added the javascript:
<script type="text/javascript">
var url = '#Url.Action("BlankEditorRow", "Consultant")';
$(document).ready(function () {
$("#addItem").click(function () {
$.ajax({
url: url,
cache: false,
success: function (html) {
alert(html);
$("#editorRows").append(html); }
});
return false;
});
});
</script>
A partial view:
#model Consultants.Models.Program
#using Consultants.Helpers
<div class="editorRow">
#*#using (Html.BeginCollectionItem("programs"))
{*#
#Html.TextBoxFor(model => model.Name)
#*}*#
</div>
And action methods in the controller:
public ActionResult Create()
{
Consultant consultant = new Consultant();
return View(consultant);
}
public ActionResult BlankEditorRow()
{
return PartialView("ProgramEditorRow", new Program());
}
Note that I commented out the Html.BeginCollectionItem part in the partial view, because I can't get that to work. It only gives me the hidden field Steven Sanderson talks about, but not the actual textbox. So I tried commenting that part out and just had a textbox. Well, that gets me the textbox, but I can't get to that info in the post method. I use the Consultant object as return parameter, but the Programs property contains no Program. Perhaps this has to do with the fact that I cannot get the BeginCollectionItem helper to work, but in any case I don't understand how to do that, or how to get to the Program supposedly added in the view. With a simple object I would add the new object in the post method by something like _repository.AddToConsultants(consultant), and when I save to EF it gets its id. But how do I do the same thing with the program object and save it to the database via EF?
Phil Haack has written a great blog post which explains how to model bind to a list. It's specific to MVC2, but I'm not sure if version 3 has improved on this.
Model Binding To A List.
In a scenario where the user is allowed to add an arbitrary number of items, you'll probably want to create new input fields using JavaScript. Steven Sanderson shows here how to achieve that:
Editing a variable length list, ASP.NET MVC 2-style.
Those resources should get you all the way there.
You may take a look at the following blog post about writing custom object templates. Also don't use EF models in your views. Design view models that are classes specifically tailored to the needs of a given view and have your controller map between the EF models and the view models that should be passed to the view. AutoMapper is a good tool that could simplify this mapping.

Resources