MVC3 binding nested model using Entity Framework with GUID keys - asp.net-mvc-3

I have been fighting with this all day and still I am failing.
I can simplify the problem as follows:
I have reports and reports have forms. I have entity models of each. They have Guid id's as shown below.
I am trying to get a single view where I can create a report and a form. As an end goal I would like to be able to add multiple forms, but just one would be great. My controller is as follows:
// GET: /AllInOne/Create
public ActionResult Create()
{
ViewBag.PossibleReportBases = reportBaseRepository.All;
ViewBag.PossibleCategories = categoryRepository.All;
var model = new Report {FromDate = DateTime.Now};
model.Forms.Add(new Form());
return View(model);
}
// POST: /AllInOne/Create
[HttpPost]
public ActionResult Create(Report report)
{
if (ModelState.IsValid) {
reportRepository.InsertOrUpdate(report);
reportRepository.Save();
return RedirectToAction("Index");
}
else
{
ViewBag.PossibleReportBases = reportBaseRepository.All;
ViewBag.PossibleCategories = categoryRepository.All;
return View();
}
}
The repository code looks like this:
public void InsertOrUpdate(Report report)
{
if (report.Id == default(System.Guid)) {
// New entity
report.Id = Guid.NewGuid();
context.Reports.AddObject(report);
} else {
// Existing entity
context.Reports.Attach(report);
context.ObjectStateManager.ChangeObjectState(report, EntityState.Modified);
}
}
At one stage the binding was giving me this error:
The EntityCollection has already been initialized. The InitializeRelatedCollection method should only be called to initialize a new EntityCollection during deserialization of an object graph.
I have tried many things for the views, but none of them have worked.
Please help.

i dont' think you need to bother with the attaching. if you've selected the report from your context, it is already being tracked. you can simplify your repository like so
public void InsertOrUpdate(Report report)
{
// i prefer Guid.Empty but no big deal
if (report.Id == default(System.Guid)) {
// New entity
report.Id = Guid.NewGuid();
context.Reports.AddObject(report);
}
context.SaveChanges();
}

Related

There is no argument given that corresponds to the required formal parameter 'photo' of 'Interface.Create(Trademark, IFormFile)'?

I am using .Net Core 5 and uploading images for my Trademark. I use Repository for my work and got error CS706: There is no argument given that corresponds to the required formal parameter 'photo' of 'Interface.Create(Trademark, IFormFile)' in Controller
_trademarkRepo.CreateNewTrademark(trademark);
Controller
public IActionResult CreateTrademark(Trademark trademark)
{
if(ModelState.IsValid)
{
_trademarkRepo.CreateNewTrademark(trademark);
}
_logger.LogInformation("...");
return RedirectToAction("Index");
}
Repo
public bool CreateNewTrademark(Trademark trademark, IFormFile photo)
{
var path = Path.Combine(this._webHostEnvironment.WebRootPath, "trademarks", photo.FileName);
var stream = new FileStream(path, FileMode.Create);
photo.CopyToAsync(stream);
if(CheckExist(trademark.TrademarkName))
{
return false;
}
var newTrademark = new Trademark
{
TrademarkName = trademark.TrademarkName,
Description = trademark.Description,
Image = photo.FileName
};
_dbContext.Trademarks.Add(newTrademark);
_dbContext.SaveChanges();
return true;
}
From error it is evident that what error is.
Method at repo level required two argument. One is trademark and another is photo.
When you have called that from controller , you have only passed one. (Trademark only and photo is missing). This is the error.
Basically your controller should look like following.
public IActionResult CreateTrademark(Trademark trademark,IFromFile photo)
{
if(ModelState.IsValid)
{
_trademarkRepo.CreateNewTrademark(trademark,photo);
}
_logger.LogInformation("...");
return RedirectToAction("Index");
}
Note: There are many other dependencies like how you post file from UI etc. That is not scope of this question and so answer. You have to look for those detail.

unit testing a Delete controller method

I am trying to test the delete method from my controller.
I have the following test case:
[TestMethod()]
[DeploymentItem("Courses.sdf")]
public void RemoveCourseConfirmedTest()
{
CoursesController target = new CoursesController();
int id = 50;
ActionResult actual;
CoursesDBContext db = new CoursesDBContext();
Course courseToDelete = db.Courses.Find(id);
List<CourseMeet> meets = db.Meets.Where(a => a.courseID == id).ToList();
actual = target.RemoveCourseConfirmed(courseToDelete);
foreach (CourseMeet meet in meets)
{
Assert.IsFalse(db.Meets.Contains(meet));
}
Assert.IsFalse(db.Courses.Contains(courseToDelete));
}
This is the controller method
[HttpPost, ActionName("RemoveCourse")]
public ActionResult RemoveCourseConfirmed(Course course)
{
try
{
db.Entry(course).State = EntityState.Deleted;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (DbUpdateConcurrencyException)
{
return RedirectToAction("RemoveMeet", new System.Web.Routing.RouteValueDictionary { { "concurrencyError", true } });
}
catch (DataException)
{
ModelState.AddModelError(string.Empty, "Unable to save changes. Try again.");
return View(course);
}
}
However when I run the test case I get the following exception.
System.InvalidOperationException: An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
I ran the test through the debugger and found that the problem in on the following line:
db.Entry(course).State = EntityState.Deleted;
I am not sure why this is happening.
The biggest problem I see here is that you are testing directly against the database. Your unit tests should mock out the database dependencies especially.
However, if you are deadset on doing it this way, then the error you are getting is coming from the fact that your test is opening a CoursesDBContext, and then your code under test appears to be using its own CoursesDBContext. So, the error is because you cannot try to reference the same entity from different contexts. You will need some way of passing in your context to your class (either a public property or being passed through the parameters)

Is this the correct way to save form values in MVC3?

Here's my code:
[HttpGet]
public ActionResult Register()
{
RegisterViewModel model = new RegisterViewModel();
using (CityRepository city = new CityRepository())
{
model.SelectCityList = new SelectList(city.FindAllCities().ToList(), "CityID", "CityName");
}
using (CountryRepository country = new CountryRepository())
{
model.SelectCountryList = new SelectList(country.FindAllCountries().ToList(), "CountryID", "CountryName");
}
return View(model);
}
[HttpPost]
public ActionResult Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
//Actually register the user here.
RedirectToAction("Index", "Home");
}
//Something went wrong, redisplay the form for correction.
return View(model);
}
Is this the best approach or is there another better tested way? Keep in mind that my database tables/field names are nothing like what I declared in my models. I have to scrape the values from the ViewModel and put them into an entity framework generated class to persist the information.
Anything here that screams out at you as wrong?
I use that pattern and another pattern which looks like this (important part is the AutoMapper part):
[HttpPost]
public ActionResult Register(RegisterViewModel model)
{
if (!ModelState.IsValid)
{
// repopulate any input or other items set in GET
// prefer to do at top due to ^^^ is easy to overlook
return View(model);
}
// if it's an edit, pull to new instance
// from the database and use automapper to
// map over the submitted values from model to instance
// then update instance in database
//
// VALUE: useful if form only shows
// some of the properties/fields of model
// (otherwise, those not shown would be null/default)
// if it's new, insert
RedirectToAction("Index", "Home");
}
That's the pattern I generally use.
I prefer this pattern:
Controller:
[HttpGet]
public ActionResult Index()
{
var cities= (from m in db.cities select m);
ViewBag.Cities= cities;
var states = (from m in db.States select m);
ViewBag.States = states;
return View();
}
[HttpPost]
public ActionResult Index(RegisterViewModel model)
{
if (ModelState.IsValid)
{
// Saving the data
return View("ActionName", model);
}
return View();
}
View:
#Html.DropDownList("DDLCities",new SelectList(ViewBag.Cities, "CityId" , "CityName" ), new { #class = "className" })
#Html.DropDownList("DDLStates",new SelectList(ViewBag.States, "StateId" , "StateName" ), new { #class = "className" })
Advised changes to [HttpGet]:
[HttpGet]
public ActionResult Register()
{
// Get
var cities = new List<City>();
var countries = new List<Country>();
using (CityRepository city = new CityRepository())
{
cities = city.FindAllCities().ToList();
}
using (CountryRepository country = new CountryRepository())
{
counties = country.FindAllCountries().ToList();
}
// Map.
var aggregatedObjects = new SomePOCO(cities, countries);
var model = Mapper.Map<SomePOCO,RegisterViewModel>(aggregatedObjects );
// Return
return View(model);
}
Summary of changes:
Layout your logic in such a way the controller's job makes sense. Get - Map - Return. Exactly the tasks (in order) for which a Controller is designed for.
Use AutoMapper to do the heavy lifting of ViewModel creation for you.
Advised changes to your [HttpPost]:
[HttpPost]
public ActionResult Register(RegisterViewModel model)
{
if (!ModelState.IsValid)
return View(model);
try
{
var dbObj = Mapper.Map<RegisterViewModel,SomeDomainObj>(model);
_repository.Save(dbObj);
return RedirectToAction("Index");
}
catch (Exception exc)
{
if (exc is BusinessError)
ModelState.AddModelError("SomeKey", ((BusinessError)exc).FriendlyError);
else
ModelState.AddModelError("SomeKey", Resources.Global.GenericErrorMessage);
}
return View(model);
}
Summary of changes:
Try/catch. Always need to capture exceptions, whether they are domain exceptions or lower-level (database ones)
Check ModelState validity first. As #Cymen says - do it first so you don't forget later
Add exceptions to ModelState. Use custom exception classes for business errors with descriptive, resource-based messages. If the error is too low-level for the user (foreign key constraint, etc), show a generic message

asp.net mvc ObjectDisposedException with ef

I need some help with this error "The ObjectContext instance has been disposed and can no longer be used for operations that require a connection."
It's a asp.net mvc3, EF4, and ms sql.
Here is the razor with two dropdowns:
<div class="editRow">
#Html.DropDownListFor(m=>m.IndustryId, (SelectList)ViewBag.Industry, #Empower.Resource.General.ddlDefaultVal, new { #class = "ddl400" })
#Html.ValidationMessageFor(m => m.IndustryId)
</div>
<div class="editRow">
#Html.DropDownListFor(m=>m.ProvinceId, (SelectList)ViewBag.Province, #Empower.Resource.General.ddlDefaultVal, new {#class = "ddl400"})
#Html.ValidationMessageFor(m => m.ProvinceId)
</div>
Controller:
IndustryService indService = new IndustryService();
ViewBag.Industry = new SelectList(indService.GetAllIndustry(), "IndustryId", "IndustryName");
ProvinceService proService = new ProvinceService();
ViewBag.Province = new SelectList(proService.GetAllProvince(), "ProvinceId", "ProvinceName");
return View();
ProvinceService:
public IEnumerable<Province> GetAllProvince()
{
using (var context = DBContext.ObjectContext)
{
var pros = context.Provinces;
return pros;
}
}
IndustryService is identical as above...
public class DBContext
{
private static EmpowerDBEntities _empowerContext;
public static EmpowerDBEntities ObjectContext
{
get
{
if (_empowerContext == null)
_empowerContext = new EmpowerDBEntities();
return _empowerContext;
}
}
}
I know the problem occurs in second dropdown when it tries to retrive data while the connection is desposed by previous query. Please help me with this, thanks.
The fix is simple - convert to a .ToList() or First() before using. LINQ has deferred execution and tries to run this command after the context is disposed (when your object results are referenced) - not when you actually make the call.. You need to force it to run now while the context is in scope.
using (var context = DBContext.ObjectContext)
{
var pros = context.Provinces;
return pros.ToList();
}
Also - your code above is checking for null in the get accessor. However this object won't be null - it will be disposed, so you cannot do your check this way, you need to check if its null and not disposed.
public class DBContext
{
private static EmpowerDBEntities _empowerContext;
public static EmpowerDBEntities ObjectContext
{
get
{
if (_empowerContext == null || _empowerContext.IsDisposed())
_empowerContext = new EmpowerDBEntities();
return _empowerContext;
}
}
}
something like that anyways :)
I ran into a similar problem. I had been following this pattern, which I had seen in many code examples on the web:
public ActionResult Show(int id)
{
using (var db = new MyDbContext())
{
var thing = db.Things.Find(id);
return View(thing);
}
}
However, this was causing the ObjectDisposedException listed above whenever I tried to access anything that hadn't been loaded into memory in the View code (in particular, one-to-many relationships in the main view model).
I found a different pattern in this example:
public class MyController : Controller
{
private MyDbContext _db;
public MyController()
{
_db = new MyDbContext();
}
public ActionResult Show(int id)
{
// Do work such as...
var thing = _db.Things.Find(id);
return View(thing);
}
protected override void Dispose(bool disposing)
{
_db.Dispose();
base.Dispose(disposing);
}
}
This pattern keeps the database connection alive until the View has finished rendering, and neatly disposes of it when the Controller itself is disposed.
Here is the problem when you are using
using(var context=new CustomerContext())
{
return View(context.Customers.ToList());
}
when the block of code executes all references are disposed that you are lazzy loading so that's why it is throwing this error.
so i used
return View(context.Customers.ToList()) directly it will work perfectly fine.

Partial ViewModels, Controllers, in Razor

Trying to get my ducks in a row with MVC3 + Razor!
I finally understand the concept of a 'View-Model' to wrap my entity classes and tailor them to a View.
Now I'm assembling a page with partial views representing different elements necessary to the page (such as drop down lists, forms, etc.) each of these will be represented by a 'View-Model' that maps to an entity class and back to my database.
First I am trying to create a partial view representing a component that is a drop-down list of elements in the database, that when selected will render another partial view, etc.
I just can't put together why I can't generate this drop-down list, and once I do how the main 'controller' maps all this together?
In short I'm curious - does each partial view need a controller even if it's based on a strongly typed model?
Breaking it down:
My Entity Model-View Wrapper (getting all the elements available from the database
*Updated* - to a working example now, note I don't think I was asking the right question before, but this will give you an idea of what I was trying to accomplish! Next step is to move all these operations 'off' the controller (and populate them in the models default constructor, for ease of use later).
CharactersListViewModel.cs
Going to move avoid the 'View Model' for now until I get a little more comfortable
Creating a partial view that displays a drop down list with the Characters' ID as a value, and name as the text, create strongly-typed view, partial view
controller for main-page in section:
HistoryController.cs
public class HistoryController : Controller
{
public ActionResult Index()
{
var list = new List<SelectListItem>();
using (var _database = new fff_newEntities())
{
foreach(Character c in (from c in _database.Characters select c)){
list.Add(new SelectListItem(){Text = c.CharacterName, Value = c.Id.ToString()});
}
}
ViewBag.Clients = list;
}
}
//
// GET: /History/Details/5
public ActionResult Details(int id)
{
return View();
}
//
// GET: /History/Create
public ActionResult Create()
{
return View();
}
//
// POST: /History/Create
[HttpPost]
public ActionResult Create(FormCollection collection)
{
try
{
// TODO: Add insert logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
//
// GET: /History/Edit/5
public ActionResult Edit(int id)
{
return View();
}
//
// POST: /History/Edit/5
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
try
{
// TODO: Add update logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
public ActionResult Delete(int id)
{
return View();
}
//
// POST: /History/Delete/5
[HttpPost]
public ActionResult Delete(int id, FormCollection collection)
{
try
{
// TODO: Add delete logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
The index to display the whole page including the partial component (my drop down list)
index.cshtml:
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#Html.DropDownListFor(x => x.CurrentCharacterId, (IEnumerable<SelectListItem>)ViewBag.Clients);
On the last line here, #Html.Action(...) where do I actually create the drop-down list?
Sorry if this seems trivial but I can't wrap my head around it and I really want to learn MVC3 + Razor correctly!
A partial view is meant to abstract out some HTML/View Logic so that it can be re-used either in multiple places or for repeating (looping).
Though you can have an action that maps to the partial and if the partial in question does some explicit data access this might be the way to go but if you're just passing down all the data it needs from the controller itself then - no, you don't need a Controller/Action for it.
Since you're doing some explicit data access I would probably make an action for it...
[ChildActionOnly]
public ActionResult Characters()
{
using (var _database = new entities())
{
CharactersViewModel viewModel = new CharactersViewModel();
viewModel.Characters = _database.Characters.ToDictionary(c => c.Id, c => c.CharacterName);
return PartialView(viewModel);
}
}
In your view...
#Html.Action("Characters")
Of course there's nothing wrong with the way you're doing it but I find having it map to an action can make things easier down the road if you ever wanted to retrieve the HTML from this rendered partial view via an ajax request or something of the sort.
Notes:
Try to wrap your entity context object in a using so it can dispose of the connection.
You can use ToDictionary to select your dictionary directly from the query scope.

Resources