Return ViewModel instead of Model - c#

The following method returns a IEnumerable MODEL called PROF.
Instead of using the model, I want to use a viewModel which contains the same fields as the Model PROF. How can i make changes to the following function to return my VIEWMODEL(which is called MyProfViewModel ) instead of the model (which is called PROF ).
public async Task<ActionResult> Index()
{
var cp= db.PROF.Include(c => c.ACCOUNTS);
return View(await cp.ToListAsync());
}
ViewModel
public class MyProfViewModel
{
public int myprofID { get; set; }
public string myprofDes{ get; set; }
}
Model
public class PROF
{
public int ID{ get; set; }
public string DESCRIPTION { get; set; }
}

Joel's answer is pretty much it. But since you mentioned you are starting I will provide a more deltailed answer to make more clear how to use.
First you define a conversion, in this case I put it in the ViewModel, but you can put it somewhere else that would make more sense in your project:
public class MyProfViewModel
{
public int myprofID { get; set; }
public string myprofDes { get; set; }
public static MyProfViewModel FromModel(PROF model)
{
var viewModel = new MyProfViewModel()
{
myprofID = model.ID,
myprofDes = model.DESCRIPTION
};
return viewModel;
}
}
Then you just apply the conversion before returning:
public async Task<ActionResult> Index()
{
var cp = PROF.Include(c => c.ACCOUNTS);
var models = await cp.ToListAsync();
var viewModels = models.Select(MyProfViewModel.FromModel);
return View(viewModels);
}
By the way, you can make the tranformation to models as well by adding this to your ViewModel class:
public PROF ToModel()
{
return new PROF()
{
ID = this.myprofID,
DESCRIPTION = this.myprofDes
};
}
Hope this helps!

Add a select statement which maps one type to another:
db.PROF.Include(c => c.ACCOUNTS)
.Select(x=> new MyProfViewModel(){myprofID = x.ID, ...});
Depending on if this select statement is supported by your ORM layer (Entity Framework?), I would do it before, or after .ListAsync();
Pros of before: could lead to better optimized SQL queries.

Related

Creating dropdownlist from database with Entity Framework Core in an ASP.NET Core 5 MVC application

In my app I'm trying to populate a drop-down menu by taking data from the db with EF Core, but unfortunately I've been trying it for two days without success.
I have tried to do this as recommended in this post, but I keep getting errors.
More specifically the error I'm facing now is
'IEnumerable' does not contain a definition for 'Years' and no accessible extension method 'Years' accepting a first argument of type 'IEnumerable' could be found
This error comes from the view:
#Html.DropDownList(m => m.Years, Model.Years, "-- Select year --")
Here is my model class:
public partial class Bdgfixmonth
{
public int Counter { get; set; }
public int Byear { get; set; }
//
public IEnumerable<SelectListItem> Years { get; set; }
public string Bbudget { get; set; }
public int Bmonth { get; set; }
public string Blongmonth { get; set; }
public int Closed { get; set; }
public string Current { get; set; }
}
And here is my controller for the GET action method Index:
private readonly salesContext _context;
public IEnumerable<SelectListItem> Years { get; set; }
public bdgfixmonthController(salesContext context)
{
_context = context;
}
public async Task<IActionResult> Index()
{
var bdgfixmonths = await _context.Bdgfixmonths.ToListAsync();
IEnumerable<SelectListItem> GetAllYears()
{
IEnumerable<SelectListItem> list = _context.Bdgfixmonths
.AsEnumerable()
.Select(s => new SelectListItem
{
Selected = false,
Text = s.Byear.ToString(),
Value = s.Byear.ToString()
});
return list;
}
Years = GetAllYears();
return View(bdgfixmonths);
}
The goal here is to populate the drop-down menu with the data of the db, and then make a query based on the selected value, so that only the relevant values are shown (in this case based on the year).
Any help would be very appreciated, thanks.
Since you're returning a model that contains a list of Bdgfixmonths, it's probably better to create a view model that looks like this:
public class BdgfixmonthsViewModel
{
public List<Bdgfixmonth> Bdgfixmonths { get; set; }
public IEnumerable<SelectListItem> Years { get; set; }
}
Then, your Action Method will change to:
private readonly salesContext _context;
public bdgfixmonthController(salesContext context)
{
_context = context;
}
public async Task<IActionResult> Index()
{
var vm = new BdgfixmonthsViewModel();
var bdgfixmonths = await _context.Bdgfixmonths.ToListAsync();
IEnumerable<SelectListItem> GetAllYears()
{
IEnumerable<SelectListItem> list = _context.Bdgfixmonths
.AsEnumerable()
.Select(s => new SelectListItem
{
Selected = false,
Text = s.Byear.ToString(),
Value = s.Byear.ToString()
});
return list;
}
vm.Years = GetAllYears();
vm.Bdgfixmonths = bdgfixmonths;
return View(vm);
}
Your ViewModel will have the two collections which can then be used in the view. You will need to update the View to reference the Bdgfixmonth properties like Bdgfixmonths.Bbudget for example. Also, the model will change to something like:
#model IEnumerable<Bdgfixmonth>
becomes
#model BdgfixmonthsViewModel
And then any iteration code will change to:
#foreach (var item in Model.Bdgfixmonths)
{
item.Bbudget
}

How to map users identity and Auditable properties in view model

This is my view model.
public class ProductViewModel
{
public Guid Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public bool IsAvailable { get; set; }
}
When form is posted from client the form is submitted to this Controller
public async Task<IHttpActionResult> AddProduct(ProductViewModel productViewModel)
{
await ServiceInstances.PostAsync("product/add", productViewModel);
return Ok();
}
Then this controller submit the form to the API controller
Which is on my separate Project.
[HttpPost]
[Route("add")]
public IHttpActionResult AddProduct(ProductViewModel model)
{
_productService.AddProduct(model.UserServiceDetails());
return Ok();
}
Extension UserServiceDetails Where i get the Login User Info
public static UserServiceDetailModel<T> UserServiceDetails<T>(this T model)
{
var serviceRequestModel = new ServiceRequestModel<T>()
{
Model = model,
LoginInfo = HttpContext.Current.User.Identity.GetUserLoginInfo();
};
}
AddProductService:
public void AddProduct(UserServiceDetailModel<ProductViewModel> serviceRequestModel)
{
var repo = _genericUnitOfWork.GetRepository<Product, Guid>();
var mapped = _mapper.Map<ProductViewModel, Product>(serviceRequestModel.Model);
mapped.Id = Guid.NewGuid();
mapped.CreatedDate = GeneralService.CurrentDate();
mapped.CreatedById = serviceRequestModel.LoginInfo.UserId;
repo.Add(mapped);
_genericUnitOfWork.SaveChanges();
}
Now my question is Is there any way to assign the value to this field CreatedDate and CreatedById before posting it to service?
Reduce these logic to mapper:
mapped.CreatedDate = GeneralService.CurrentDate();
mapped.CreatedById = serviceRequestModel.LoginInfo.UserId;
Or is there any way that those field gets mapped to Product when
var mapped = _mapper.Map<ProductViewModel, Product>(serviceRequestModel.Model);
Sometime i may have the List<T> on view-model and there i have to add this field using the loop.
So this same mapping may get repeated over and over on Add Method Or Update.
In some entity i have to assign the ModifiedDate and ModifiedById also.
My Mapper Configuration:
public class ProductMapper : Profile
{
public ProductMapper()
{
CreateMap<ProductViewModel, Product>();
}
}
I cannot add the Enitity as IAuditableEntity and Overrride in ApplicationDbContext because my DbContext is in separate Project and i donot have access to Identity there.

ASP.NET MVC How to get FilePath pass into PeopleDB?

I'm a newbie to ASP.NET using Entity Framework. I have different models for People, FileType and FilePath. I want to display the image by retrieving the file path from FilPath together with data like name, age, etc. in index view. I made it happen in Detail view, but in index view page I received error as "Value can not be null", which caused by the FilePath in PeopleDB is null.
Below is my code, please help. Thanks.
/Model/PeopleDB.cs
namespace MvcDemo.Models {
public class PeopleDB
{
public PeopleDB()
{
this.FilePaths = new HashSet<FilePath>();
}
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string Address { get; set; }
public string Interests { get; set; }
public ICollection<FilePath> FilePaths { get; set; }
}
public class PeopleDBContext : DbContext
{
public DbSet<FilePath> FilePaths { get; set; }
public DbSet<PeopleDB> People { get; set; }
}
}
/Model/FilePath.cs
namespace Assessment_HC.Models
{
public class FilePath
{
public int FilePathId {get;set;}
[StringLength(255)]
public string FileName {get;set;}
public FileType FileType {get;set;}
public int PersonID {get;set;}
public virtual PeopleDB Person {get;set;}
}
}
Moedel/FileType.cs
namespace Assessment_HC.Models
{
public enum FileType
{
Avatar = 1, Photo
}
}
Here is the controller for index view
//Get: /People/Index
public ActionResult Index()
{
return View(db.People.ToList());
}
In db.People.ToList(), People.FilePath view is null.
In the controller, the detail view is like this, from where I can get the image showing on detail page:
// GET: /People/Details
public ActionResult Details(int id = 0)
{
PeopleDB peopledb = db.People.Find(id);
PeopleDB people = db.People.Include(i => i.FilePaths).SingleOrDefault(i => i.ID == id);
if (peopledb == null)
{
return HttpNotFound();
}
return View(peopledb);
}
Thanks for your help. Let me know if you need more code.
Based on comments, It seems the only thing you should do is changing FilePaths property of your PeopleDB to be virtual to work with Lazy Loading (which is enabled by default):
public virtual ICollection<FilePath> FilePaths { get; set; }
Lazy Loading is enabled by default, and as stated in comments you didn't change it and there is nothing about Lazy Loading in your context constructor, So it seems the problem is in your FilePaths navigation property that is not virtual.
For index action:
return View(db.People.ToList());
For details action its better to do like:
var people = db.People.Where(x => x.ID == id).FirstOrDefault();
if (people == null)
{
return HttpNotFound();
}
return View(people );
But any way, If disable lazy Loading, you should use Include to include your navigation property in result. In this situation you can load data in your index action use:
db.People.Include(x => x.FilePaths).ToList()
or
//Remember to add using System.Data.Entity;
db.People.Include("FilePaths").ToList()
And to disable Lazy Loading you can
db.Configuration.LazyLoadingEnabled = true;
Or in the constructor of your context:
this.Configuration.LazyLoadingEnabled = false;
More information:
Loading Related Entities
Lazy loading is the process whereby an entity or collection of
entities is automatically loaded from the database the first time that
a property referring to the entity/entities is accessed. When using
POCO entity types, lazy loading is achieved by creating instances of
derived proxy types and then overriding virtual properties to add the
loading hook.
I've tested the code, the only one thing that you need is enabling Eager loading using Include method:
public ActionResult Index()
{
var _db = new ApplicationDbContext();
var model = _db.People.Include("FilePaths").ToList();
return View(model);
}
In this case all related file paths will be loaded.
You can also make FilePaths as virtual:
public virtual ICollection<FilePath> FilePaths { get; set; }
And change your query this way:
var model = _db.People.ToList();
In both cases, all related file paths will be loaded.

How to update a collection inside an entity within a post action in ASP.NET MVC5?

I have created an ASP.NET MVC5 sample project. I created my entities and from that, scaffolded the controllers for CRUD operations. I can only edit the POD members with the scaffolded code. I want to be able to add/remove related entities.
With my current code, when I click save there is no error but no related entities are modified (POD data is modified though). For example, if I wanted to remove all players from the account, they aren't removed. What am I doing wrong?
How can I remove/add related entities and push those changes to the database?
Here is the form:
Here is the action to update the entity:
public async Task<ActionResult> Edit([Bind(Include = "Account,Account.AccountModelId,Account.Name,Account.CreatedDate,SelectedPlayers")] AccountViewModel_Form vm){
if (ModelState.IsValid){
if (vm.SelectedPlayers != null){
vm.Account.PlayerModels = db.PlayerModels.Where(p => p.AccountModel.AccountModelId == vm.Account.AccountModelId).ToList();
foreach (var player in vm.Account.PlayerModels){
player.AccountModel = null;
db.Entry(player).State = EntityState.Modified;
}
vm.Account.PlayerModels.Clear();
foreach (var player_id in vm.SelectedPlayers){
var player = db.PlayerModels.Where(p => p.PlayerModelId == player_id).First();
vm.Account.PlayerModels.Add(player);
db.Entry(player).State = EntityState.Modified;
}
}
db.Entry(vm.Account).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(vm);
}
Here are the models:
public class AccountViewModel_Form{
public AccountModel Account { get; set; }
public HashSet<Int32> SelectedPlayers { get; set; }
public virtual List<PlayerModel> PlayersList { get; set; }
}
public class AccountModel{
public AccountModel(){
PlayerModels = new HashSet<PlayerModel>();
}
public Int32 AccountModelId { get; set; }
public string Name { get; set; }
public DateTime CreatedDate { get; set; }
public virtual ICollection<PlayerModel> PlayerModels { get; set; }
}
public class PlayerModel{
public Int32 PlayerModelId { get; set; }
public float Gold { get; set; }
public string Name { get; set; }
public virtual AccountModel AccountModel { get; set; }
}
I'm basically lost. I can't find any examples in how to update related data. Could someone point me in the right direction?
I come from Symfony (PHP Framework) background. I thought it would be easier but I have been having problems.
Basically I was missing the Attach function and that I had to force the load on the collection to make it work.
I found how to attach a non-attached entity here: Model binding in the controller when form is posted - navigation properties are not loaded automatically
When you post the data, the entity is not attached to the context, and when you try to save changes to a complex entity, the context makes a mess.
The code is a little different because I was trying to make it work at home. But it is essentially the same models.
public ActionResult Edit(AccountEditViewModel vm)
{
if (ModelState.IsValid)
{
//I was missing these 2 important lines...
db.Accounts.Attach(vm.Account);
db.Entry(vm.Account).Collection(a => a.Players).Load();
if (vm.SelectedPlayers != null)
{
foreach (var player in vm.Account.Players.ToList())
{
if (vm.SelectedPlayers.Contains(player.Id) == false)
{
player.Account = null;
vm.Account.Players.Remove(player);
db.Entry(player).State = EntityState.Modified;
vm.SelectedPlayers.Remove(player.Id);
}
}
foreach (var player_id in vm.SelectedPlayers)
{
var player = db.Players.Where(p => p.Id == player_id).First();
player.Account = vm.Account;
vm.Account.Players.Add(player);
db.Entry(player).State = EntityState.Modified;
}
}else
{
vm.Account.Players.Clear();
}
db.Entry(vm.Account).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(vm);
}

ASP.NET MVC model binding - nested properties

I have a model:
public class DataModel
{
public GridSortOptions SortOptions { get; set; }
public string Term { get; set; }
public int? Page { get; set; }
...
}
public class GridSortOptions
{
public string Column { get; set; }
public SortDirection Direction { get; set; }
}
And a http request: ?Column=LastName&Direction=Descending
That request will not work, i need to update it as follow: ?SortOptions.Column=LastName&SortOptions.Direction=Descending
Is it possible to teach ASP.NET to understand the original request without changing the model?
PS. I know that I could create a custom binder but I feel that there is a much simple way exist...
Thanks in advance!
I have mine structured slightly differently, as I came across the same problem (in MVC1 and still have it now - mvc4) . I have often found that having nested models tend to cause headaches and unnecessary complexity, so I try to flatten my view models as much as possible, just to simplify things, so don't have to write custom model binders, saves a lot of time and code.
My action typically looks method looks like this
//...snip
public ActionResult List(int? page, GridSortOptions sortOptions, string keyword) {
var model = new UserGridViewModel();
IQueryable<User> users = new UserRepository(Session).List();
if (sortOptions.Column != null) {
users = users.OrderBy(sortOptions.Column, sortOptions.Direction);
}
if (keyword != null) {
users = users.Where(x => x.Name.Contains(keyword))
}
model.SortOptions = sortOptions;
//using MvcContrib.Pagination.PaginationHelper here
model.Results = users.AsPagination(page ?? 1, 20);
return View(model);
}
//.....
My view logic is simply:
#using MvcContrib.UI.Grid
#using MvcContrib.UI.Pager
#model UsersGridViewModel
#Html.Grid(Model.Results).Columns(cols => {
cols.For(col => col.Id).Sortable(true);
cols.For(col => col.Name).Sortable(true);
//...etc
}).Sort(Model.SortOptions)
My grid view models are normally like this:
public class UserGridViewModel
{
public IPagination<User> Results { get; set; }
public GridSortOptions SortOptions { get; set; }
}

Categories