I have a problem with posting a multiselect, I get error:
"There is no ViewData item with the key 'NotificationUsergroups' of type 'IEnumerable'."
In the controller I have:
MultiSelectList NotificationUsergroups = new MultiSelectList(Usergroups, "UsergroupID", "UsergroupName", selectedNotificationUsergroupIDs);
ViewData["NotificationUsergroups"] = NotificationUsergroups;
In the view I have:
<%= Html.ListBox("NotificationUsergroups")%>
And in the post action:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ObjectEdit([BindAttribute(Include = "BookingObjectID,BookingObjectName,Activated,ActivationStartDate,ActivationEndDate,AvalibleObjects,AvalibleObjectsPerBooking")]BookingObject bookingobject, int[] Objectcategories, int[] NotificationUsergroups, int[] CancellationUsergroups)
{
if (ModelState.IsValid)
{
try
{
_bs.SaveBookingObject(bookingobject);
if (NotificationUsergroups != null)
_bs.SaveNotificationUsergroups(bookingobject.BookingObjectID, NotificationUsergroups);
return View("CreateObject", new BookingObjectsAdminEditViewModel { BookingObject = bookingobject });
}
catch {
ModelState.AddModelError("SomeError", "errrrrrrrrror");
}
}
What might be wrong? I've checked the spelling and all, works if I dont run with the multiselect listing.
What makes the data "disappear"?
Thanks in advance
/M
You will need to set the viewdata in the POST accepting method as well because if it has an error it will return to the original view.
Kindness,
Dan
Or include them in your model :) You would need to alter view tho.
Your action must setup your ViewData.
If your action calls
ViewData["NotificationUsergroups"] = NotificationUsergroups;
everything should be alright.
Related
Hope someone can help me with this.
I have a controller in my ASP.NET MVC project that is used for editing a so called survey form. The survey form has a 1-to-1 relation with objects of type Form. When saving the survey form I also want to set the name of the form. However the form property is null. The FormID property has the correct value (using database first, EF5). Here is the problem.
[HttpPost]
public ActionResult Edit(SurveyForm surveyform)
{
if (ModelState.IsValid)
{
db.Entry(surveyform).State = EntityState.Modified;
// This cannot be done because surveyform.Form is null
surveyform.Form.Name = "Wish this would work!";
db.SaveChanges();
}
}
My question is: how can I 'attach' surveyform to the model so that is loads related data?
Thanks in advance!
At a guess, I'd say the entity being POSTed back isn't being tracked, so Entity Framework doesn't realise it's from the database. The line db.Entry(surveyForm) gets the entry matching the POSTed form, but you're not retaining it. Try this:
[HttpPost]
public ActionResult Edit(SurveyForm surveyform)
{
if (ModelState.IsValid)
{
var formEntry = db.Entry(surveyform);
formEntry.State = EntityState.Modified;
formEntry.Form.Name = "This might work...";
db.SaveChanges();
}
}
Hopefully, the .Entry() will get you the database's version. Unfortunately, you'll probably have to copy the property values accross, although you might find you can use .Attach() and then copy over the navigation properties from the database's version.
Having said that, it's generally a good idea to not use your database models in the view if you can help it; separation of concerns and all that. If there's more than a couple of properties on that model that are needed for the database but not the view (or vice versa) then you might want to use a local view model for it and just copy the properties to a freshly-retrieved database entity.
Try:
db.Entry(surveyform).Reference(x => x.Form).Load();
surveyform.Form = db.Entry(surveyform).Reference(x => x.Form).CurrentValue;
In your model, make sure you have a navigation propery like this:
public virtual Form Form { get; set; }
And then in your DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<SurveyForm>().HasOptional(p => p.Form);
}
HasOption can be replaced with HasRequired.
I have never had the exact issue you are having, so I hope this helps.
Eventually I combined multiple suggestions above and ended up with the following.
I added the following to my view:
#Html.TextBox("formName", Model.Form.Name)
and then changed the code in my controller to:
[HttpPost]
public ActionResult Edit(string formName, SurveyForm surveyForm)
{
if (ModelState.IsValid)
{
var surveyFormEntry = db.Entry<SurveyForm>(surveyForm);
db.SurveyForms.Attach(surveyForm);
surveyFormEntry.State = EntityState.Modified;
surveyFormEntry.Reference(x => x.Form).Load();
surveyForm.Form = db.Entry(surveyForm).Reference(x => x.Form).CurrentValue;
surveyForm.Form.Name = formName;
db.SaveChanges();
return RedirectToAction("Index", new { surveyId = surveyForm.SurveyID });
}
It was a lot of trial and error. A way of programming I so disapprove on and dislike but I'm happy it finally works.
Thanks all!
Need to compare the original value with the current value in order to do some updates on objects prior to save changes. How to get the original value of an object? The following does not work:
public ActionResult Edit(int id = 0)
{
Project project = db.Projects.Find(id);
...
db.Projects.Attach(project); // ATTACH here
...
}
[HttpPost]
public ActionResult Edit(Project project)
{
if (ModelState.IsValid)
{
db.Entry(project).State = EntityState.Modified; // The STATE was DETACHED here...
...
}
Is this a configuration issue, or did I miss something? Thanks in advance for any help!
The problem with your solution is the lifetime of the controller. By default a new instance of the controller is generated for each client request. This means that you need to load the original value inside the POST method of edit, because you cannot share the instance of your context.
I have edited the code sample after #Gerard's comment
[HttpPost]
public ActionResult Edit(Project project)
{
if (ModelState.IsValid)
{
var original_value = db.Projects.Find(project.ProjectId);
or
var original_value_detached = db.Projects.AsNoTracking().Where(P => P.ProjectId == project.ProjectId).FirstOrDefault();
}
}
Or you write your own implementation of the controller factory to share you context between two requests, but you should consider requests from different clients. You can find an example here
It looks like you are implementing concurrency. May be, you can try to catch your changes using DbUpdateConcurrencyException. Something like this:
try
{
...
db.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var dbValue = (Project)entry.GetDatabaseValues().ToObject();
if(dbValue.State == EntityState.Modified)
{
//***your code
}
}
I have modified a Model recently for a system I've inherited, and for some reason the ViewBag.Model is not making into the page. I've reverted the code back to before I started tinkering, and still no luck.
The call into the view is as follows:
public virtual ActionResult Edit(long id)
{
var _news = _newsRepository.GetNewsById(id);
ViewBag.Model = AutoMapper.Mapper.Map<News, NewsModel>(_news);
ViewBag.Model.CurrentNewsImageFile = ConfigSettings.HostDomainName + ConfigSettings.NewsImageBasePath + _news.image_file;
return View();
}
The view has the following code:
#model MyModels.Models.NewsModel
#{
bool IsCreate = Model == null || Model.Id == 0;
ViewBag.Title = IsCreate ? "Add News" : "Edit News";
}
The problem is that "Model" is always null in the view code... what have I missed? Am I missing a fundamental here?
When tracing through the ActionResult code, right up until the return View() the debug inspector correctly shows the Model containing everything I would expect it too.
You need to simply return View(theModel). If you don't pass the model into View() it doesn't know what to do.
However, I'm confused about the code you have listed, you seem to be trying to put the model into the ViewBag? The ViewBag shouldn't contain the model.
I think the code you want is:
public virtual ActionResult Edit(long id)
{
var _news = _newsRepository.GetNewsById(id);
var model = AutoMapper.Mapper.Map<News, NewsModel>(_news);
model.CurrentNewsImageFile = ConfigSettings.HostDomainName + ConfigSettings.NewsImageBasePath + _news.image_file;
return View(model);
}
I think it is the AutoMapper line. Once you have created your mappings I believe you need to them call them using the following syntax:
var newsModel = Mapper.Map(_news);
I want to render the same view after a successful action (rather than use RedirectToAction), but I need to modify the model data that is rendered to that view. The following is a contrived example that demonstrates two methods that that do not work:
[AcceptVerbs("POST")]
public ActionResult EditProduct(int id, [Bind(Include="UnitPrice, ProductName")]Product product) {
NORTHWNDEntities entities = new NORTHWNDEntities();
if (ModelState.IsValid) {
var dbProduct = entities.ProductSet.First(p => p.ProductID == id);
dbProduct.ProductName = product.ProductName;
dbProduct.UnitPrice = product.UnitPrice;
entities.SaveChanges();
}
/* Neither of these work */
product.ProductName = "This has no effect";
ViewData["ProductName"] = "This has no effect either";
return View(product);
}
Does anyone know what the correct method is for accomplishing this?
After researching this further, I have an explanation why the following code has no effect in the Action:
product.ProductName = "This has no effect";
ViewData["ProductName"] = "This has no effect either";
My View uses HTML Helpers:
<% Html.EditorFor(x => x.ProductName);
HTML Helpers uses the following order precedence when attempting lookup of the key:
ViewData.ModelState dictionary entry
Model property (if a strongly typed view. This property is a shortcut to View.ViewData.Model)
ViewData dictionary entry
For HTTP Post Actions, ModelState is always populated, so modifying the Model (product.ProductName) or ViewData directly (ViewData["ProductName"]) has no effect.
If you do need to modify ModelState directly, the syntax to do so is:
ModelState.SetModelValue("ProductName", new ValueProviderResult("Your new value", "", CultureInfo.InvariantCulture));
Or, to clear the ModelState value:
ModelState.SetModelValue("ProductName", null);
You can create an extension method to simplify the syntax:
public static class ModelStateDictionaryExtensions {
public static void SetModelValue(this ModelStateDictionary modelState, string key, object rawValue) {
modelState.SetModelValue(key, new ValueProviderResult(rawValue, String.Empty, CultureInfo.InvariantCulture));
}
}
Then you can simply write:
ModelState.SetModelValue("ProductName", "Your new value");
For more details, see Consumption of Data in MVC2 Views.
The values are stored in ModelState.
This should do what you want:
ModelState.SetModelValue("ProductName", "The new value");
I wouldn't suggest doing that though... the correct method would be to follow the PRG (Post/Redirect/Get) pattern.
HTHs,
Charles
EDIT: Updated to reflect the better was of setting the ModelState value as found by #Gary
This will trigger the model to re-evaluate under simple conditions:
ModelState.Clear();
model.Property = "new value";
TryValidateModel(model);
Perform ModelState.Clear() before you change the model.
...
ModelState.Clear()
dbProduct.ProductName = product.ProductName;
dbProduct.UnitPrice = product.UnitPrice;
...
i have a dropdown list which select a value
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Screenname(FormCollection collection)
{
Viewdata["screenname"] = collection[0];
return RedirectToAction("Index", new { ScreenName = ViewData["screenname"] });
}
then i want to access this ViewData in other actions like this
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection collection, string screenname)
{
try
{
/// thats my dataobject which creates
DataObj.SaveData(Guid.Empty, collection, screenname);
return RedirectToAction("Index", new { ScreenName = ViewData["screenname"] });
}
catch
{
return View("Error");
}
}
where index looks like this ...
public ActionResult Index(string ScreenName)
{
///thats my list
GetTable = new GetDataTable(ScreenName);
return View(GetTable);
}
First when i select the value and index gets executed properly.... but when i try to access the viewdata again it doesn't contain the value so anybody if please can help ...
or alternate method to save and retrieve data .
The ViewData object is specific for the particular action that is executing. To pass data between actions, use TempData. more on the difference between the two on MSDN.
You can also directly write to the session state through the Controller.Session property.
This has actually been covered quite often here. The solution for now is to use TempData to save the data you need before you use RedirectToAction().
If you do a search for "RedirectToAction" you'll find a number of posts covering this topic, such as this one.
The next official release of the framework will fix this.
I used a view to take the data from the user and then saved it to a static variable and then used this variable to pass the data to all the other views .
Thanks anyways