Why does the Edit screen show the value I'm editing but when I try to save, the Edit HTTPPOST has a null object?
Getting a an error I've tracked down but don't see the cause of. I have a class used for a drop list, so it only has AdvisoryTypeID and AdvisoryType. But now I'm creating screens for users to see all in a grid (Index), add new (Create), and change existing ones (Edit). The index screen reads from the DB no problem. So does Edit. Create doesn't need to.
But both Edit and Create give the same error on HTTPPOST. The error is "The Value 'whatever I typed in' is invalid". Debugging the code turned up why: the "advisoryTypes" variable is null for reasons I don't understand:
CONTROLLER (for Edit post)
[Authorize]
[HttpPost]
public ActionResult Edit(AdvisoryTypes advisoryType)
{
try
{
if (ModelState.IsValid) //never get past this because it's null above
etc....
}
I have compared this to another screen that does Edit and Create fine and they're identical (except field names, for example). I'm stumped.
MODEL
public class AdvisoryTypes
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int AdvisoryTypeID { get; set; }
[Display(Name = "Type")]
[Required]
public string AdvisoryType { get; set; }
}
CONTROLLER (for Edit Get)
[Authorize]
public ActionResult Edit(int id = 0)
{
AdvisoryTypes advisoryType = db.AdvisoryType.Find(id);
if (advisoryType == null)
{
return HttpNotFound();
}
return View(advisoryType);
}
VIEW
model TheNewSolution.Models.AdvisoryTypes
#{
ViewBag.Title = "Edit Advisory Type";
}
<h2>Edit Advisory Type</h2>
#*<br />*#
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Advisory</legend>
<table>
<tr>
<td>
#Html.HiddenFor(model => model.AdvisoryTypeID)
<div class="editor-label">
#Html.LabelFor(model => model.AdvisoryType)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AdvisoryType)
#Html.ValidationMessageFor(model => model.AdvisoryType)
</div>
</td>
</tr>
</table>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
PART TWO
Now some odd background: this is the first model/controller/view I created since updating EF from 5 to 6 alpha3 (had to because I had POCO and edmx in same project and this solved that bug). I created the model manually. When I tried to create the Controller via the wizard, the wizard gave an error "Unable to retrieve metadata for ProjectName.Models.AdvisoryProviders'. Unable to cast obect of type 'System.Data.Entity.Core.Objects.ObjectContext' to type 'System.Data.Objects.ObjectContext'.".
The result was having to manually create the controller by copying/pasting another one and making changes, then creating my views the same way. I'm suspicious this is all related but maybe not. If this proves unrelated I'll remove this and post as another question.
the error from EF of "Unable to retrieve metadata for ProjectName.Models.AdvisoryProviders i have seen before, but this has only been because i was using MySQL, the only way i found around this kind of error and to make sure everything worked was to use http://visualstudiogallery.msdn.microsoft.com/72a60b14-1581-4b9b-89f2-846072eff19d to create models from the database, and then use http://blog.stevensanderson.com/2011/01/13/scaffold-your-aspnet-mvc-3-project-with-the-mvcscaffolding-package/ to create the controllers, with view, rather than the buggy version of create view for EF.
I posted about these problems a while back with EF5 and its a real pain, MVCScaffolding seems to handle that pain alot better than the built in TT templates with MVC 4
hope this helps
I am not sure why this resolved, but here's what I did. I needed to create the screens mentioned above (index with grid, create, edit) for three different things (types, providers, categories) in my app. I did the first, type, resulting in the above issues.
I decided to create the same for "providers" by copying and pasting the controllers and views from "type" screens, then changing the model and field names as needed, expecting the screen to have the same bugs. But the screens all worked. I did it again for "categories", which also worked. Then I deleted my failing Create and Edit screens for "type", and recreated them from the "providers" screens. And they worked.
I have no explanation.
Related
I have one controller (RegisteredController.java) , and I want that the output of the controller is displayed in the JSP (its name is commment_form.jsp). So I use a forEach tag in the jsp to display a list of comments (the comments which the user has inserted about a given resource). For "resource" I usually mean an image. So there are a list of comments about an "image" and I want that all the comments are all displayed in the bottom page, when a comment is going to be inserted into the comment form. My question is how must be written the code into the controller in order to set the output for the jsp ? Should I use a #ModelAttribute , a put-attribute or something else ? Here is the code of the controller and of the jsp :
The comment_form.jsp is:
<form:form modelAttribute="comments">
<table class="commento">
<tr>
<th/>
<th>ID</th>
<th>Contenuto</th>
</tr>
<c:forEach items = "${comments}" var="comment">
<tr>
<td><c:out value="${comments.content}"></c:out></td>
<td><c:out value="${comments.content}</c:out></td>
</c:forEach>
</table>
</form:form>
The RegisteredController.java is:
#RequestMapping("/comment.do")
public String comment(#ModelAttribute Comment comment, BindingResult
bindingResult, Model model, Locale locale) {
User user=userService.getUserCurrent();
comment.setDatePubblication(SQLUtility.getCurrentDate());
comment.setIdUser(user.getId());
commentService.create(comment);
Object[] args = { comment.getId() };
String message = messageSource.getMessage("message.update", args,locale);
List<Comment> comments =
commentService.findAllCommentByResource(comment.getIdResource());
model.addAttribute("comments", comments);
model.addAttribute("id",comment.getIdResource());
model.addAttribute("message", message);
model.addAttribute("comment", comment);
return "redirect:/registered/comment_start.do";
}
Please any help ? I will appreciate . Thanks you.
In case of redirect pass additional data as redirect attributes.
To carry data across a redirect use RedirectAttributes#addFlashAttribute(key, value).
What Java doc says:
A RedirectAttributes model is empty when the method is called and is never used unless the method returns a redirect view name or a RedirectView.
After the redirect, flash attributes are automatically added to the model of the controller that serves the target URL.
Read more...
One extra note :
In JSP it should be ${comment.content} instead of ${comments.content}
I work on an asp.net mvc project with Breeze.
I have a page where I display details about a transport and list linked transports in a table below it.
Consider the following entities:
public class Transport
{
[Key]
public int Id { get; set; }
public string TransportNumber { get; set; }
public string Description { get; set; }
public virtual List<LinkedTransport> LinkedTransports { get; set; }
...
}
public class LinkedTransport
{
[Key, Column(Order = 0)]
public int TransportId { get; set; }
[Key, Column(Order = 1)]
public int TransportRelatedId { get; set; }
public virtual Transport Transport { get; set; }
public virtual Transport TransportRelated { get; set; }
}
So these 2 entities allows me to define my transports and (for each) linked transports.
First, I load my (main) transport into an observable named transport:
var query = entityQuery.from('Transports')
.where('id', '==', transportId)
.expand("LinkedTransports.transportRelated");
query = query.using(breeze.FetchStrategy.FromServer);
Please pay attention to the expand where I retrieve also the linked transports.
Now I have the following code for showing my transport and linked transports:
<div data-bind="with: transport()">
<input type="text" data-bind="text: transportNumber"></input>
<input type="text" data-bind="value: description"></input>
...
<table data-bind="foreach: linkedTransports">
<tr>
<td data-bind="text: transportRelated().transportNumber()"></td>
<td data-bind="text: transportRelated().description()"></td>
</tr>
</table>
</div>
So far, so good. I can display my main transport informations and also linked transports informations. Now I need to let the user add some linked transports. So I have the following code:
var createLinkedTransport = function (transpId, transpRelatedId) {
var initialValues = ({
transportId: transpId,
transportRelatedId: transpRelatedId
});
manager.createEntity(entityNames.linkedTransport, initialValues);
};
At runtime, when I call this function, I can add a linked transport to my (main) transport.
My problem: the added element is not correctly showed in my foreach knockout loop. I see that a new row is added to the table but this one is empty. I think the problem is because I don't expand any information on my newly added element but I don't know how to proceed.
Any idea how to proceed?
UPDATE
To be clear, when I add a linkedTransport to my transport, immediately after the add (and without reloading the page) I cannot read properties of the targetted (*) linkedTransport. BUT if I saveChanges AND reload my page, THEN I can read properties of the targetted linkedTransport.
When I say 'the targetted linkedTransport' I mean the transport which is referenced by my TransportRelatedId (>> public virtual Transport TransportRelated).
So If I add the linkedTransport #123 to the transport #456 THEN immediately (without reloading the page) I cannot display the Description property of the #123.
<div data-bind="with: transport()">
...
<div data-bind="foreach: linkedTransports">
<label data-bind="text: transportRelated().description()"></label>
</div>
</div>
Hope I'm clear.
UPDATE 2
As suggested by Ward, I set a breakpoint immediately after adding the linkedTransports and check the transport().linkedTransports(). Below is the results:
transport().linkedTransports()[0].transport() >> the properties are there
transport().linkedTransports()[0].transportId() >> 1
transport().linkedTransports()[0].transportNumber >> '123456' ("dummy" property, does not help)
transport().linkedTransports()[0].transportRelated() >> null
transport().linkedTransports()[0].transportRelatedId() >> 5 (the id of the linkedTransport)
So my problem is that the transportRelated() is null.
UPDATE 3
Finally I got it working. It is important to have the added (referenced) element in cache when it is added. For my case I do something like:
ctxTransport.getTransportById(5, obs);
ctxTransport.createLinkedTransport(1, 5);
So before calling the createLinkedTransport I call the getTransportById with the 5th id (the id of the linkedTransport).
Anothing important thing is how we display the linkedTransports on our page:
<div data-bind="foreach: linkedTransports">
<span class="input-control text">
<!-- ko with: transportRelated -->
<!-- /ko -->
<i class="icon-remove btn" data-bind="click: $root.removeLinkedTransport" style="cursor:pointer;"/>
</span>
</div>
Pay attention to the ko with: transportRelated followed by <a href="#" data-bind="text: id()". At first I do wrong <a href="#" data-bind="text: transportRelated().id()". We cannot proceed like that.
Updated Answer
The short of it: the related transport entity wasn't showing up because it was not in cache.
Mr. B had the ID of the related entity and used that id while creating the new transport-link entity. But that id referred to an entity which is not in cache. Therefore, the navigation property to the related transport entity returned ... null.
Breeze navigation properties do not lazy load (as a server-side ORM such as EF might do). You have to ensure that the referenced entities are in cache.
This is a design choice, not an omission. In our experience, lazy loading leads to misuse which leads to performance problems that are blamed on the framework. We decided to bypass this particular hassle .. at least for now.
See the comments for more information.
Perhaps as important ... see how we diagnosed the problem. Good lessons learned there.
Original "wrong" Answer
[This answer turned out to be "wrong" but it lead to the "right" answer. Preserved for continuity.]
How is EF treating your LinkedTransport entity? It looks like it lacks data properties of its own; it only has foreign keys to related entities. Could it be that EF regards LinkedTransport as the junction table of a many-to-many association.
As you probably know, Breeze does not (yet ... and won't soon) support many to many. What happens if you give this table a data property ... even a dummy data property such as a Boolean? That will force EF to treat it as its own entity type ... with a compound key. The OrderDetail in Northwind has this same structure: a compound key (Order, Product) and small pay load (quantity, price).
There is probably another way to tell EF not to treat this as many-to-many without adding a dummy property. I just don't remember what that is.
In my database, I have 40 tables that contain only an ID number and a name. My database is accessed using Entity Framework. While I have no trouble editing them each by generating a strongly-typed view and postback methods for each object, I would like to create a more generic method and view for viewing and editing these objects.
I am currently using the following code to access each object. In this case, it is for an object of 'AddressType':
public ActionMethod EditAddressType(int ID)
{
var result = database.AddressType.Single(a => a.ID == ID);
View(result);
}
[HttpPost]
public ActionMethod EditAddressType(int ID, FormCollection formValues)
{
var result = database.AddressType.Single(a => a.ID == ID);
UpdateModel(result);
database.SaveChanges();
return View("SaveSuccess");
}
The view 'EditAddressType' is strongly typed and works fine, but there's a lot of repeated code (one instance of this for each object). I've been told that I need to use reflection, but I'm at a loss for how to implement this. My understanding is that I need to retrieve the object type so I can replace the hardcoded reference to the object, but I'm not sure how to get this information from the postback.
I've had success binding the information to ViewData in the controller and passing that to a ViewPage view that knows to look for this ViewData, but I don't know how to postback the changes to a controller.
Thanks for any help you can give me!
If you are going to edit the object you don't need to refetch it from the database in your POST action. The first thing would of course be to abstract my data access code from the controller:
public class AddressesController: Controller
{
private readonly IAddressesRepository _repository;
public AddressesController(IAddressesRepository repository)
{
_repository = repository;
}
public ActionMethod Edit(int id)
{
var result = _repository.GetAddress(id);
return View(result);
}
[HttpPut]
public ActionMethod Update(AddressViewModel address)
{
_repository.Save(address);
return View("SaveSuccess");
}
}
You will notice that I have renamed some of the actions and accept verbs to make this controller a bit more RESTFul.
The associated view might look like this:
<% using (Html.BeginForm<AddressesController>(c => c.Update(null))) { %>
<%: Html.HttpMethodOverride(HttpVerbs.Put) %>
<%: Html.HiddenFor(model => model.Id) %>
<%: Html.TextBoxFor(model => model.Name) %>
<input type="submit" value="Save" />
<% } %>
As far as the implementation of this IAddressesRepository interface is concerned, that's totally up to you: Entity Framework, NHibernate, XML File, Remote Web Service call, ..., that's an implementation detail that has nothing to do with ASP.NET MVC.
I'm new to MVC!
I am trying to use two DropDownLists (Cities, Categories) in a PartialView that will be used in MasterPage, meaning they will be visble all the time.
I tried to load them in HomeCOntroller, but that didn't work. I got an Exception.
I read something about making a baseController that the other controllers will inherit from, I have tried that, kind of, but I guess i'm doing something wrong.
This is the only code I got today:
Masterpage
<% Html.RenderPartial("SearchForm"); %>
PartialView (SearchForm.ascx)
<% using (Html.BeginForm("Search", "Search")) { %>
<% } %> // dont know why I need two BeginForms, if I dont have this the other form won't trigger at all! Weird!
<% using (Html.BeginForm("Search", "Search", FormMethod.Get)) { %>
<%= Html.DropDownList("SearchForm.Category", new SelectList(ViewData["Categories"] as IEnumerable, "ID", "Name", "--All categories--")) %>
<%= Html.DropDownList("Search.City", Model.Cities, "--All cities--") %>
<input name="search" type="text" size="16" id="search" />
<input type="submit" id="test" title="Search" />
<% } %>
Two question:
Where and how to load the DropDownLists is the problem. I have tried to load it in the HomeController, but when go to another page then it says that the DDLs is empty and I get a Excecption.
Why do I have to use two forms for the ActionMethod to trigger ?
Hope anyone can help me out!
It sounds like you're only setting the property for a single action result. The Model.Cities data will have to be populated for every single view that needs to use it.
One solution would be to move the population of it to an ActionFilter
public class CityListAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext) {
var result = filterContext.Result as ViewResult;
result.ViewData.Model = //populate model
base.OnActionExecuted(filterContext);
}
}
and then add the filter to your controller
[CityList]
public class HomeController : Controller {
public ActionResult Index() {
return View();
}
}
As for the two forms issue, there should be no reason that i can think of that you need an empty form.
Take a look at the html that's being output and make sure it's ok. Also check the action is being generated correcly
Better way to do this, is to create something like MasterController and have action method on it like this:
[ChildActionOnly]
public ActionResult SearchForm()
{
//Get city data, category data etc., create SearchFormModel
return PartialView(model);
}
I recommend you create strongly typed view (SearchForms.ascx of type ViewUserControl<SearchFormModel>). Also it may be a good idea to have a model like this:
public class SearchViewModel
{
public IList<SelectListItem> Cities { get; set; }
public IList<SelectListItem> Categories { get; set; }
}
and use a helper like this: http://github.com/Necroskillz/NecroNetToolkit/blob/master/Source/NecroNet.Toolkit/Mvc/SelectHelper.cs to convert raw data to DDL friendly format beforehand.
In any case, you now use Html.RenderAction() instead of Html.RenderPartial() and specify you want "SearchForm" action from "MasterController".
Say I have a strongly-typed view of ViewPage<Song> and some additional fields in the form of create/edit scenarios that are not directly part of the Song entity (because I have to do some parsing before I fetch the composers from a foreign table and create the bridge entities SongComposers).
public ActionResult Create(Song song, string composers) {
if (ModelState.IsValid) {
// redirect to details
}
// return a view with errors
return View(song);
}
And in the view
<% using (Html.BeginForm()) { %>
<%= Html.EditorForModel() %>
<div class="editor-label"><label for="composers">Composers</label></div>
<div class="editor-field"><input id="composers" type="text" name="composers" /></div>
<% } %>
Now when I try to create a food and get validation errors, the previously entered form values for fields in Html.EditorForModel get filled properly but the composers field is blank after a submit. How can I fix this (ie. make it so that it "remembers" what the user wrote in the composers field)?
ViewData["composers"] = composers;