Multiple query results on one view - c#

I'm trying to learn C#.net and figured with all the hype around .net core I'd start there, but I'm a little out of my depth.
I have a list of (lets say 'Countries') from a database. I click on a country and it shows me details of the item. This works:
// GET: Database/Details/00000000-0000-0000-0000-000000000000
public async Task<IActionResult> Details(Guid? id)
{
if (id == null)
{
return NotFound();
}
var country = await _context.countries.SingleOrDefaultAsync(s => s.Id == id);
if (subscription == null)
{
return NotFound();
}
return View(country);
}
On the details view, I want to show two tables.
The first being 'country', which will show generic information (Capital, Currency, Continent). I can currently do this fine.
The second being a list of cities (from a table called 'Cities'). How would I do this? I can't work out how to return a second result set to the view.
(Hope the analogy helped explain it!)

You need to return a ViewModel. So create a class IndexViewModel or how your action is called (this is just a best practice, you can name it how you want) and add 2 properties:
public class IndexViewModel
{
public Country Country { get; set; }
public List<City> Cities { get; set; }
}
Then in your controller return the model:
// GET: Database/Details/00000000-0000-0000-0000-000000000000
public async Task<IActionResult> Details(Guid? id)
{
if (id == null || subscription == null)
{
return NotFound();
}
var model = new IndexViewModel();
model.Country = await _context.countries.SingleOrDefaultAsync(s => s.Id == id);
model.Cities = SomeFunction();
return View(model);
}
In your View add a reference at the top of the document:
#model IndexViewModel
And, you can access the data by:
#Model.Country
#Model.Cities

You will need to create a ViewModel that gives you a place to store both lists and pass that to your view. Example below.
public class MyViewModel{
public IEnumerable<Country> Countries { get; set; }
public IEnumerable<City> Cities { get; set; }
}

Related

context is null after adding new column/property

I've been modifying my existing project in mvc-c# and add a new column in one my tables.
Now, I modified my model and add this column polads
[Table("media_order_headers")]
public class OrderHeader
{
[Key]
public int id { get; set; }
/*list of other columns here */
public byte active { get; set; }
public byte with_package { get; set; }
public byte polads { get; set; } //newly added column
So now, i have added this to my Add and Edit controllers
[HttpPost]
public async Task<IActionResult> Add([FromBody]OrderHeaderViewModel model)
{
OrderHeader ord = new OrderHeader
{
/*list of other property value assigning*/
confirmed = model.confirmed,
with_package = model.with_package,
polads = model.polads
};
_context.OrderHeader.Add(ord);
await _context.SaveChangesAsync();
[HttpPost]
public async Task<IActionResult> Edit([FromBody]OrderHeaderViewModel model)
{
var ord = await _context.OrderHeader.Where(f => f.id == model.id).FirstOrDefaultAsync<OrderHeader>();
if (ord != null)
{
ord.with_package = model.with_package;
ord.billing_guide_no = model.bgnumber;
ord.polads = model.polads;
await _context.SaveChangesAsync();
I have not encountered a problem on Add function but in Edit Function.
It doesnt show exception error but it says on the console is internal server error 500. My variable ord is null. When I _context for internal exceptions it shows object not set to an instance of an object.
I have put a breakpoint on this line of script:
var ord = await _context.OrderHeader.Where(f => f.id == model.id).FirstOrDefaultAsync<OrderHeader>();
The view is returning set of values from the form.
my question is that, what might be the reason for this null error if it works with Add controller?
Any idea will be appreciated.
Thanks !

When and how to initialize (View)models using constructors with the aim to set all props of a object correctly and forget none

So I have this simple Edit action method which builds a viewmodel
public async Task<IActionResult> Edit(int id)
{
var product = await _productRepo.Get(id);
if (product == null) return RedirectToAction(nameof(Index));
var categories = await _categoryRepo.Get();
var suplliers = await _supplierRepo.Get(); ;
var editVM = new EditVM()
{
Product = product,
Categories = categories,
Suppliers = suplliers
};
return View(editVM);
}
And for completeness here is the EditVM
public class EditVM
{
public Product Product { get; set; }
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<Supplier> Suppliers { get; set; }
}
I obviously use it to edit a product. So far so good.
Now I wanted to reuse this EditVM and my Edit.cshtml view for creating products so I created the Create action method
public async Task<IActionResult> Create()
{
var categories = await _categoryRepo.Get();
var editVM = new EditVM()
{
Product = Product(),
Categories = categories
//Notice I forget to initialize Suppliers here!!
};
return View("Edit", editVM);
}
I got this null error because I forgot to initialize the Suppliers prop in my Create action method. So I improved it by changing my EditVM to only be initialized with a constructor with the required params and set the props to private setters:
public class EditVM
{
public EditVM(Product product, IEnumerable<Category> categories, IEnumerable<Supplier> suppliers)
{
Product = product;
Categories = categories;
Suppliers = suplliers;
}
public Product Product { get; private set; }
public IEnumerable<Category> Categories { get; private set; }
public IEnumerable<Supplier> Suppliers { get; private set; }
}
And now use it like:
public async Task<IActionResult> Edit(int id)
{
var product = await _productRepo.Get(id);
if (product == null) return RedirectToAction(nameof(Index));
var categories = await _categoryRepo.Get();
var suplliers = await _supplierRepo.Get();
var editVM = new EditVM(product, categories, suppliers);
return View(editVM);
}//And the same way in my Create action method
So now I can never forget to set anything and enforced the EditVM is always set with the required props. Is this style recommended? The EditVM is so plain simple...Its my expirience that when viemodels(or 'ajax return models' grow bigger programmers tend to forget some props. We can enforce this by using constructors with private setters or factories. Is this the right way to go? And in this case I just want to have a simple list with all the suplliers using _supplierRepo.Get() in my Create and my Edit action method. But what if I use a very complex query. Then I might want to pass in my repos as params to my EditVM constructor like this:
public EditVM(Product product, ICategoryRepo categoryRepo, ISupplierRepo supplierRepo)
{
Product = product;
Categories = categoryRepo.Get().Result;//My repos are async...
Suppliers = supplierRepo.Get().Result;//Possible very complex query to be only done at one location
}
Any thoughts about my question are most welcome.

how to save data from two models (relation one to many) from one viewModel in MVC c#?

I need to have only one view and :
a) create new Customer and Address or
b) For existing Customer add new Address
But I dont known what is wrong with my Save action.
How to set Customer's AddressId to new Address Id ( jus created) ?
I use:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Address> Addresses { get; set; }
}
I have two models:
public class Customer
{
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Name { get; set; }
public Address Address { get; set; }
public int? AddressId { get; set; }
}
public class Address
{
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Town { get; set; }
}
And one viewModel
public class CustomerAddressViewModels
{
public Customer Customer { get; set; }
public Address Addresses { get; set; }
}
Then I create a controller with good working Details action
public class CustomerDetailsController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: CustomerDetails/Details/5
public async Task<ActionResult> Details(int id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Customer customer = await db.Customers
.Include(c => c.Address)
.SingleOrDefaultAsync(c => c.Id == id);
return View("CustomerAddressView");
}
}
I wrote Save action for Create and Update cases:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Save(Customer customer, Address address)
{
if (!ModelState.IsValid)
{
var vieModel = new CustomerAddressViewModels();
return View("CustomerAddressView", vieModel);
}
if (customer.AddressId == 0)
{
address.StreetName = customer.Address.StreetName;
db.Addresses.Add(address);
}
else
{
var addressInDb = db.Addresses
.Single(a => a.Id == customer.AddressId);
addressInDb.StreetName = customer.Address.StreetName;
}
if (customer.Id == 0)
{
db.Customers.Add(customer);
}
else
{
var customerInDb = db.Customers
.Single(c => c.Id == customer.Id);
customerInDb.Name = customer.Name;
customerInDb.AddressId = customer.AddressId;
}
await db.SaveChangesAsync();
return RedirectToAction("Index","Customers");
}
I need to have only one view and :
a) create new Customer and Address or
b) For existing Customer add new Address
But I dont known what is wrong with my Save action.
How to set Customer's AddressId to new Address Id ( jus created) ?
When inserting an address you should add the address to the customer.Addresses collection rather than to db.Address directly, then EF should handle populating the keys for you.
There are some other things worth mentioning here I think.
You should use a viewmodel class that represents the objects being passed to and from your views instead of using your entities directly. So I'd recommend a class like this:
public class CustomerViewModel
{
public int CustomerOd { get; set; }
//<... other properties for customer>
public AddressViewModel Address { get; set; }
}
public class AddressViewModel
{
//Address propties
}
This allows you to have view specific properties on your object that can help with various things (like whether to hide or show a section for example) based on a value that isn't inside your Customers entity.
Then your controller Action save looks like this:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Save(CustomerViewModel model)
{
//preceeding code
var customer = db.Customers.SingleOrDefault(x => x.CustomerId == model.CustomerId)
if (customer.Address == null)
{
var address = new Address()
{
StreetName = model.Address.StreetName
};
Customer.Addresses.Add(address);
}
}
You then map or populate the data to your Entity from the ViewModel object. It involves a little extra work but allows for more flexibility.
What's happening in your Details action? You get customers but then don't pass it to your view?
Customer customer = await db.Customers
.Include(c => c.Address)
.SingleOrDefaultAsync(c => c.Id == id);
return View("CustomerAddressView");
In order for the framework to correctly bind your object from the form to your view for the address object, you should describe your html objects in the same structure of your object. So you could have an input like this
<input value="#Model.Address.StreetName" name="Address.StreetName"/>
and it should bind that value to the address object when you post back.
Hope this helps.

MVC 5 one to many virtual ICollection returning null?

I have a model which looks like this
public class MyModel
{
....
public virtual ICollection<Comm_ent> Comments { get; set; }
}
and the submodels look like
public class Comm_ent
{
...
public virtual My_Model MyModel{ get;set;}
}
In my Comm_ent Datatable I have a MyModelId key created by migration but when I do
public actionresult MyModelIndex(int? id)
{
var mymodel = _context.MyModels.singleordefault(u => u.Id == id)
return View(mymodel);
}
The #Model.Comments is null even though I have entries in Comm_ent datatable. This was working in MVC4 but after upgrade to MVC5 it returns null result. Any ideas how to do this in MVC5?
Try to add Include method:
public actionresult MyModelIndex(int? id)
{
var mymodel = _context.MyModels.Include(u => u.Comments).singleordefault(u => u.Id == id)
return View(mymodel);
}
Don't forget to add the following using statement:
using System.Data.Entity;
Try initializing the collection in the constructor for MyModel:
public MyModel(){
Comments = new HashSet<Comm_ent>();
}

An object being added to a list is not being stored in a database

I am developing an C# MVC 4 internet application and I have a question about adding an item to a list.
Here is my class code:
public class MapLocationCompany
{
public MapLocationCompany()
{
MapLocationList = new List<MapLocation>();
}
[Key]
public int id { get; set; }
public List<MapLocation> MapLocationList { get; set; }
}
public class MapLocationCompanyContext : DbContext
{
public DbSet<MapLocationCompany> MapLocationCompanies { get; set; }
}
I am creating a MapLocation object to add to the MapLocationList in the MapLocationCompany class.
Here is my code:
private MapLocationCompanyContext mlcc = new MapLocationCompanyContext();
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(int id, MapLocation maplocation)
{
if (ModelState.IsValid)
{
MapLocationCompany mapLocationCompany = new MapLocationCompany();
mapLocationCompany = cs.getMapLocationCompany(id);
mapLocationCompany.MapLocationList.Add(maplocation);
mlcc.SaveChanges();
return RedirectToAction("MapLocations", "Company", new { id = id });
}
return View(maplocation);
}
Here is the cs.getMapLocationCompany function that is in a different class
public MapLocationCompany getMapLocationCompany(int id)
{
return db.MapLocationCompanies.Where(c => c.id == id).FirstOrDefault();
}
The db is a MapLocationCompanyContext object.
The maplocation object in the Create function is not being stored in the database.
Can I please have some help with this code?
Thanks in advance
EDIT
Can I please have some help to add the MapLocation object to the list using the Context?
Here is the code I am currently working on:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(int id, MapLocation maplocation)
{
if (ModelState.IsValid)
{
MapLocationCompany mapLocationCompany = new MapLocationCompany();
mapLocationCompany = cs.getMapLocationCompany(id);
mlcc.MapLocationCompanies.Where(c => c.id == id).FirstOrDefault().Add(maplocation);
mlcc.SaveChanges();
return RedirectToAction("MapLocations", "Company", new { id = id });
}
return View(maplocation);
}
This is the error I am getting:
'CanFindLocation.Classes.MapLocationCompany' does not contain a definition for 'Add' and no extension method 'Add' accepting a first argument of type 'CanFindLocation.Classes.MapLocationCompany' could be found
How can I add an object to the list, knowing the id of the MapLocationCompany?
You are trying to add a MapLocation directly to the MapLocationCompany object here:
mlcc.MapLocationCompanies.Where(c => c.id == id).FirstOrDefault().Add(maplocation);
That is incorrect. You want to add it to the MapLocationList property of a MapLocationCompany object. This might make it clearer:
var company = mlcc.MapLocationCompanies.Where(c => c.id == id).FirstOrDefault();
if (company != null) {
company.MapLocationList.Add(mapLocation);
}

Categories