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?
Related
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.
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();
}
Problem Statement:
In one of my edit view I want make textbox as disabled, so for this i'm using DisplayFor instead of TextboxFor. But when I use Displayfor, alignment is not coming properly. You can see images attached for your reference. I'm using Bootstrap CSS 3.0
Any help would be appreciated. What inline CSS I should use to align properly??
Image1: In this image you can see that Acquire Status ID label and Textboxfor properly aligned.
Image2: When I use DisplayFor instead of Textboxfor, Acquire Status ID label and Displayfor are not aligned properly.
View:
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.AcquirestatusID, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DisplayFor(model => model.AcquirestatusID,new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.AcquirestatusID)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Name, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Name)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
I solved my problem using readonly attribute to my Textboxfor.
#Html.TextBoxFor(model => model.AccessoriesID,new { #class = "form-control",#readonly="readonly" })
After applying above line of code my edit view looks:
It might be happening due to the style.css which is by default in your project.
Remove style.css from the project
Note: you might only need some of the validation classes inside it
I agree with csoueidi , check with developer tools on IE or inspect element on chrome to see which css is loading the styles for that textbox.
If you want to line up the text and not use a textbox, use the class "form-control-static"
<div class="col-md-10 form-control-static">#Html.DisplayTextFor(model => model.AcquirestatusID)</div>
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.
<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.