Hey all
I am attempting to learn MVC 2 and ASP etc through the MVC Music Store. At the same time I am attempting to conform what it is doing to a solution I am developing at work. The overall structure is an IT Help Desk ticket system and I am working on the very broad admin functions of creating, editing, and deleting tickets. I have followed the tutorial very closely but have hit a brick wall, when attempting to use values that should be getting posted to controller methods, and theyre not getting there.
For the create section my create.aspx looks like
<h2>Create</h2>
<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm()) {%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Create New Request</legend>
<%: Html.EditorFor(model => model.request,
new {Softwares = Model.SoftwareName, Systems = Model.SystemIDNo}) %>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
<div>
<%: Html.ActionLink("Back to List", "Index") %>
</div>
the partial view its calling is workRequest.ascx
<p>
<%= Html.LabelFor(model => model.Medium)%>
<%= Html.TextBoxFor(model => model.Medium)%>
<%= Html.ValidationMessageFor(model => model.Medium)%>
</p>
<p>
<%= Html.LabelFor(model => model.Summary)%>
<%= Html.TextBoxFor(model => model.Summary)%>
<%= Html.ValidationMessageFor(model => model.Summary)%>
</p>
<p>
<%= Html.LabelFor(model => model.Details)%>
<%= Html.TextAreaFor(model => model.Details)%>
<%= Html.ValidationMessageFor(model => model.Details)%>
</p>
<p>
<%= Html.LabelFor(model => model.WorkHalted)%>
<%= Html.TextBoxFor(model => model.WorkHalted)%>
<%= Html.ValidationMessageFor(model => model.WorkHalted)%>
</p>
<p>
<%= Html.LabelFor(model => model.Frequency)%>
<%= Html.TextBoxFor(model => model.Frequency)%>
<%= Html.ValidationMessageFor(model => model.Frequency)%>
</p>
<p>
<%= Html.LabelFor(model => model.StartDate)%>
<%= Html.TextBoxFor(model => model.StartDate, String.Format("{0:g}", Model.StartDate))%>
<%= Html.ValidationMessageFor(model => model.StartDate)%>
</p>
<p>
<%= Html.LabelFor(model => model.SoftwareID) %>
<%= Html.DropDownList("SoftwareID", new SelectList(ViewData["Softwares"] as IEnumerable, Model.SoftwareID)) %>
</p>
<p>
<%= Html.LabelFor(model => model.SystemID) %>
<%= Html.DropDownList("SystemID", new SelectList(ViewData["Systems"] as IEnumerable, Model.SystemID)) %>
</p>
and the post create controller looks like
[HttpPost]
public ActionResult Create(WorkRequest newRequest)
{
try
{
storeDB.AddToWorkRequests(newRequest);
storeDB.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
I put a break point in the try and checked the values coming into newRequest and everything in newRequest is null, like nothing is getting passed.
A similar situation occurs on the edit side of things as well, nothing is getting sent from the partial view to the controller at all.
Anyways I am sure its something fairly simple, I am new to MVC, ASP, C#, pretty much all of it. I dont normally ask other people for much, but I have been looking at this problem for quite some time and could use some fresh eyes on this one.
Thanks in advance!
Try using the same view model type as parameter that you used to render the view and to which it is strongly typed:
[HttpPost]
public ActionResult Create(ViewModelUsedToStronglyTypeTheView model)
{
//model.request should be properly bound here
try
{
storeDB.AddToWorkRequests(model.request);
storeDB.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View(model);
}
}
Or using a prefix:
[HttpPost]
public ActionResult Create([Bind(Prefix = "request")] WorkRequest newRequest)
{
//model.request should be properly bound here
try
{
storeDB.AddToWorkRequests(newRequest);
storeDB.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
I believe you just need to change your action signature to look like this..
public ActionResult Create(WorkRequest request)
The reason for this is the name of the values being posted. They look something like RequestViewModel.Request.Summary so the name of the request object is actually Request. There is no Request property on a WorkRequest object so nothing binds.
You can bind to the object your view is tied to, RequestViewModel, as Darin Dimitrov suggested.
Alternatively, you can just bind to the request as you intended, you just need to ensure MVC knows how to bind the values. It only knows these things via naming convention so if you are binding a sub-object you need to ensure your param for the action is named the same as the property of it's parent object.
Related
I have a page that charges a credit card. When I attempt to charge the card, I'd like to redisplay the page if I get an error back as a response.
Here's my controller method:
[HttpPost]
public ActionResult Charge(CreditCardViewModel viewModel)
{
if (ModelState.IsValid)
{
var request = new AuthorizationRequest(viewModel.CreditCardNumber,
viewModel.ExpirationDate.Value.ToString("MMyy"),
viewModel.Amount.Value, "");
var gate = new Gateway("XXXXXXXXX", "XXXXXXXXX", true);
var response = gate.Send(request);
if (!response.Approved)
{
ModelState.AddModelError("", response.Message);
return View(viewModel);
}
else
{
viewModel.ResponseMessage = response.Message;
return View("Results", viewModel);
}
}
return View(viewModel); // validation error, so redisplay same view
}
And my view:
<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm("Charge", "CreditCard", FormMethod.Post)) { %>
<div class="editor-label"><%: Html.LabelFor(m => m.CreditCardNumber) %></div>
<div class="editor-field">
<%: Html.TextBoxFor(m => m.CreditCardNumber)%>
<%: Html.ValidationMessageFor(m => m.CreditCardNumber)%>
</div>
<div class="editor-label"><%: Html.LabelFor(m => m.ExpirationDate) %></div>
<div class="editor-field">
<%: Html.TextBoxFor(m => m.ExpirationDate)%>
<%: Html.ValidationMessageFor(m => m.ExpirationDate)%>
</div>
<div class="editor-label"><%: Html.LabelFor(m => m.Amount) %></div>
<div class="editor-field">
<%: Html.TextBoxFor(m => m.Amount)%>
<%: Html.ValidationMessageFor(m => m.Amount)%>
</div>
<div class="buttons">
<input type="submit" value="Charge Amount" />
</div>
<% Html.ValidationSummary(false); %>
<% } %>
The code works correctly - if I get an error back as a response, the view is reloaded. The only problem is that no error is displayed by the validation summary.
One strange thing is that if I change the AddModelError line to:
ModelState.AddModelError("CreditCardNumber", response.Message);
It will show the error next to the CreditCardNumber textbox. But I'd like to display the error in the summary below the form, as sometimes the error may not be with the credit card.
Try this (pay attention to colon) as it returns MvcHtmlstring:
<%: Html.ValidationSummary(false) %>
It is ok to give empty string, it will be treated as not field error.
Add another ValidationMessage output.
<%= Html.ValidationMessage("GatewayError") %>
And set the error message accordingly
ModelState.AddModelError("GatewayError", response.Message);
I have the following controller Action:
public ActionResult Details(int id)
{
Seguimiento seguimiento = repo.GetSeguimiento(id);
if (seguimiento == null)
{
return View("NotFound");
}
else
{
return View("Details", seguimiento);
}
}
And in the details view:
<fieldset>
<legend>Informacion General</legend>
<p><span class="label-for">ID:</span> <%: Model.ID %></p>
<p><span class="label-for">Materia:</span> <%: Model.Materia.Nombre %></p>
<p><span class="label-for">Docente:</span> <%: Model.Materia.Docente.Nombre %></p>
</fieldset>
<fieldset>
<legend>Informacion Tiempo</legend>
<p><span class="label-for">Fecha:</span> <%: String.Format("{0:g}", Model.Dia) %></p>
<p><span class="label-for">Hora:</span> <%: String.Format("{0:g}", Model.Hora) %></p>
<p><span class="label-for">Evaluador:</span> <%: Model.Evaluador.Nombre %></p>
</fieldset>
<fieldset>
<legend>Desarrollo</legend>
<p><span class="label-for">Objetivo:</span> <%: Model.Desarrollo.Dominio %> | </p>
<p><span class="label-for">Objetivo:</span> <%: Model.Desarrollo.Contenido %> | </p>
<p><span class="label-for">Objetivo:</span> <%: Model.Desarrollo.Organizacion %> | </p>
</fieldset>
The first two fieldsets work; the last one doesn't.
Dominio, Contenido, Organizacion are bit type is the database, so maybe that's a factor?
It is probably the case that Model.Desarrollo (or Model.Materia or Model.Evaluador) is null, not Dominio etc.
This should be pretty easy to figure out. Put a break point in the method and check if the property Desarollo is null. If so, you'll need to modify your view code to account for this (or fix the reason why it is null when it shouldn't be).
How do I debug code in the View in asp.net mvc2 application?
Edit after progress last night:
Ok so now I have the following:
in Shared\EditorTemplates\Equipment.ascx :
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<DAT.Models.Item>" %>
<% using (Html.BeginForm()) {%>
<%: Html.ValidationSummary(true) %>
<div class="editor-label">
<%: Html.Label("Item ID") %>
<%: Html.TextBoxFor(model => model.ItemID) %>
<%: Html.ValidationMessageFor(model => model.ItemID) %>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.ModelID) %>
<%: Html.DropDownListFor(x => x.Model.Model1, new SelectList(Model.Model.Model1, "ModelId", "Model", Model.ModelID)) %>
<%: Html.ValidationMessageFor(model => model.ModelID) %>
</div>
...
in Item\Edit.aspx :
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<DAT.ViewModels.ItemEditViewModel>" %>
<% using (Html.BeginForm()) {%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<%: Html.EditorFor(model => model.Item) %>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
The controller:
public ActionResult Edit(int id)
{
var models = from p in database.Models.Where(x => x.Model1 != null) select p;
var viewModel = new ViewModel
{
Things = database.Things.Single(a => a.ItemID == id),
//tried this:
//Models = database.Models
Models = models
};
return View(viewModel);
}
So I am sure that the problem is with this line
<%: Html.DropDownListFor(x => x.Model.Model1, new SelectList(Model.Model.Model1, "ModelId", "Model", Model.ModelID)) %>
When generating the selectlist, I don't have IEnumerable for the first parameter? Or one of the values I am feeding this is causing null. How do I get the list of models in my view?
EDIT AFTER PULLING ALL MY HAIR OUT:
It seems the problem lies in this example: http://www.asp.net/mvc/tutorials/mvc-music-store-part-4 . Strangely, I am not sure if it is following best practice. Look at the code and how they pass the models about - it seems stupidly obscure using ViewData["Blah"] and the Models as well, why can't you just have it all sent as the model? Look at the code how they have done it:
Album.ascx :
<%# Import Namespace="MvcMusicStore"%>
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MvcMusicStore.Models.Album>" %>
<script src="/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script>
<p>
<%: Html.LabelFor(model => model.Title)%>
<%: Html.TextBoxFor(model => model.Title)%>
<%: Html.ValidationMessageFor(model => model.Title)%>
</p>
<p>
<%: Html.LabelFor(model => model.Price)%>
<%: Html.TextBoxFor(model => model.Price)%>
<%: Html.ValidationMessageFor(model => model.Price)%>
</p>
<p>
<%: Html.LabelFor(model => model.AlbumArtUrl)%>
<%: Html.TextBoxFor(model => model.AlbumArtUrl)%>
<%: Html.ValidationMessageFor(model => model.AlbumArtUrl)%>
</p>
<p>
<%: Html.LabelFor(model => model.Artist)%>
<%: Html.DropDownList("ArtistId", new SelectList(ViewData["Artists"] as IEnumerable, "ArtistId", "Name", Model.ArtistId))%>
</p>
<p>
<%: Html.LabelFor(model => model.Genre)%>
<%: Html.DropDownList("GenreId", new SelectList(ViewData["Genres"] as IEnumerable, "GenreId", "Name", Model.GenreId))%>
</p>
View:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcMusicStore.ViewModels.StoreManagerViewModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Edit - <%: Model.Album.Title %>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Edit Album</h2>
<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm()) {%>
<fieldset>
<legend>Edit Album</legend>
<%: Html.EditorFor(model => model.Album, new { Artists = Model.Artists, Genres = Model.Genres}) %>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
<div>
<%:Html.ActionLink("Back to Albums", "Index") %>
</div>
</asp:Content>
View Model:
using System.Collections.Generic;
using MvcMusicStore.Models;
namespace MvcMusicStore.ViewModels
{
public class StoreManagerViewModel
{
public Album Album { get; set; }
public List<Artist> Artists { get; set; }
public List<Genre> Genres { get; set; }
}
}
And the controller:
//
// GET: /StoreManager/Edit/5
public ActionResult Edit(int id)
{
var viewModel = new StoreManagerViewModel
{
Album = storeDB.Albums.Single(a => a.AlbumId == id),
Genres = storeDB.Genres.ToList(),
Artists = storeDB.Artists.ToList()
};
return View(viewModel);
}
So it looks to me like the model is built in the controller as makes logical sense to me. Then in the view they use this statement which causes my confusion:
<%: Html.EditorFor(model => model.Album, new { Artists = Model.Artists, Genres = Model.Genres}) %>
The model is being split/changed and now we send the other (Artists, Genres) as ViewData ??
Can someone explain, and is this at all fitting with the entire design pattern?
You could put a breakpoint in your controller action and analyze your model and view data.
This being said, why are you using a strongly typed view and ViewData at the same time? Make sure that ViewData["Models"] as IEnumerable is not null (in your controller) or even better get rid of it and put it in your model as a strongly typed property. Also I would recommend you using the strongly typed helper DropDownListFor:
<%: Html.DropDownListFor(
x => x.ModelID,
new SelectList(Model.Models, "ModelId", "Model", Model.ModelID)
)%>
I have the following table structure:
I'm trying to populate a combobox of all the Jefes when editing an Area. Meaning I can change who's in charge of an area.
Here is my AreaController.cs code:
public ActionResult Edit(int id)
{
Area area = areaRepository.GetArea(id);
JefeRepository jefe = new JefeRepository();
ViewData["Jefes"] = new SelectList(jefe.FindAllJefes(), "Nombre", "Nombre");
return View(area);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues)
{
Area area = areaRepository.GetArea(id);
try
{
UpdateModel(area);
areaRepository.Save();
return RedirectToAction("Details", new { id = area.ID });
}
catch (Exception)
{
foreach (var issue in area.GetRuleViolations())
{
ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
}
return View(area);
}
}
Note that .FindAllJefes() returns an IQueryable<> collection.
Now in my Edit.aspx file I have the following:
<fieldset>
<legend>Informacion Detallada de Area | <%: Model.Nombre %></legend>
<div class="editor-label">
<%: Html.LabelFor(model => model.Nombre) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.Nombre) %>
<%: Html.ValidationMessageFor(model => model.Nombre) %>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.IDJefe) %>
</div>
<div class="editor-field">
<%: Html.DropDownList("IDJefe", (SelectList)ViewData["Jefes"], "(Select Jefe)") %>
<%: Html.ValidationMessageFor(model => model.IDJefe) %>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
I receive this error:
There is no ViewData item of type
'IEnumerable' that has
the key 'IDJefe'.
Per MSDN :
new SelectList(jefe.FindAllJefes(), "Nombre", "Nombre");
jefe.FindAllJefes() should be an IEnumerable when passed to a SelectList ctor. Your argument which should read jefe.FindAllJefes().ToList()
Without seeing how your custom classes are designed, this is a tough one, but just to hazard a guess:
ViewData["Jefes"] = new SelectList(jefe.FindAllJefes(), "Nombre", "Nombre")
I don't know how your SelectList class is setup, but (maybe) you're using "Nombre" as the ID AND the display value (???).
So your select list is looking for a key called "IDJefe", and not finding it, because you used "Nombre" twice.
<div class="editor-field">
<%: Html.DropDownList("IDJefe", (SelectList)ViewData["Jefes"], "(Select Jefe)") %>
<%: Html.ValidationMessageFor(model => model.IDJefe) %>
</div>
In C# ASP.NET MVC application I use Link to SQL to provide data for my application. I have got simple database schema like this:
In my controller class I reference this data context called Model (as you can see on the right side of picture in properties) like this:
private Model model = new Model();
I've got a table (List) of Series rendered on my page. It renders properly and I was able to add delete functionality to delete Series like this:
public ActionResult Delete(int id) {
model.Series.DeleteOnSubmit(model.Series.SingleOrDefault(s => s.ID == id));
model.SubmitChanges();
return RedirectToAction("Index");
}
Where appropriate action link looks like this:
<%: Html.ActionLink("Delete", "Delete", new { id=item.ID })%>
Also create (implemented in similar way) works fine. However edit does not work. My edit looks like this:
public ActionResult Edit(int id) {
return View(model.Series.SingleOrDefault(s => s.ID == id));
}
[HttpPost]
public ActionResult Edit(Series series) {
if (ModelState.IsValid) {
UpdateModel(series);
series.Title = series.Title + " some string to ensure title has changed";
model.SubmitChanges();
return RedirectToAction("Index");
}
return View(series);
}
I have controlled that my database has a primary key set up correctly. I debugged my application and found out that everything works as expected until the line with model.SubmitChanges();. This command does not apply the changes of Title property(or any other) against the database.
Please help.
EDIT:
If I add this line: model.Series.Attach(series); just before model.SubmitChanges(); there is no change - edits still does not reflect to database. The instance passed to Edit method as a parameter is already attached to the data context model.
EDIT:
Code of view that belongs to method Edit:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<TVSeriesInfoApp.Models.Series>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Edit
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Edit</h2>
<% using (Html.BeginForm()) {%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
<%: Html.LabelFor(model => model.Title) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.Title) %>
<%: Html.ValidationMessageFor(model => model.Title) %>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.Seasons) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.Seasons) %>
<%: Html.ValidationMessageFor(model => model.Seasons) %>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.Stars) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.Stars) %>
<%: Html.ValidationMessageFor(model => model.Stars) %>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
<div>
<%: Html.ActionLink("Back to List", "Index") %>
</div>
</asp:Content>
This is what our Edit Action would look like (Adjusted for your model):
[HttpPost]
public ActionResult Edit(int id, Series series)
{
Series updatingSeries = model.Series.Single(s => s.ID == id);
try
{
TryUpdateModel(updatingSeries);
model.SubmitChanges();
return RedirectToAction("Details", new { id = updatingSeries.ID });
}
catch
{
return View(updatingSeries);
}
}
This can happen because the ModelState might not be valid in some cases. Have you done something to the View? Can you also post your View Code here please?
First, never ever "delete" with HTTP GET (this is exactly what you're doing with Html.ActionLink("Delete", "Delete", new { id=item.ID }).
As for edits, you first have to Attach your instance to the DataContext.