ASP.NET MVC What is the best way to use ViewModels? - asp.net-mvc

Customers.cs
public partial class Customers
{
public int sno { get; set; }
public string CustomerName { get; set; }
public string CustomerNo { get; set; }
...
// 20 more attribute too...
}
Cities.cs
public partial class Cities
{
public int sno { get; set; }
public string CityName { get; set; }
public string CityPlate { get; set; }
public string CityPhoneCode { get; set; }
}
AddCustomerViewModel.cs
public class AddCustomerViewModel
{
[Required(ErrorMessage = "Şehir seçiniz.")]
[Display(Name = "Şehir")]
public Nullable<int> CityId { get; set; }
// same with Customers.cs
public int sno { get; set; }
[Required(ErrorMessage = "Müşteri adını giriniz!")]
[Display(Name = "Müşteri Adı")]
public string CustomerName { get; set; }
[Required(ErrorMessage = "Müşteri numarası giriniz!")]
[Display(Name = "Müşteri Numarası")]
public string CustomerNo { get; set; }
...
// 20 more attribute too...
}
Controller
[Authorize(Roles = "Administrator")]
public ActionResult AddCustomer()
{
AddCustomerViewModel addCustomerViewModel = new AddCustomerViewModel();
addCustomerViewModel.Cities = entity.Cities;
return View(addCustomerViewModel);
}
[HttpPost]
[Authorize(Roles = "Administrator")]
public ActionResult AddCustomer(AddCustomerViewModel addCustomerViewModel)
{
entity.Customers.Add(GetCustomerFromViewModel(addCustomerViewModel));
entity.SaveChanges();
return View(addCustomerViewModel);
}
I m using a function that is called GetCustomerFromViewModel to convert addCustomerViewModel to Customer like below:
GetCustomerFromViewModel()
private Customers GetCustomerFromViewModel(AddCustomerViewModel addCustomerViewModel)
{
Customers customer = new Customers();
customer.CityId = addCustomerViewModel.CityId;
customer.CreatorUserId = (Guid)System.Web.Security.Membership.GetUser().ProviderUserKey;
customer.CustomerName = addCustomerViewModel.CustomerName;
customer.CustomerNo = addCustomerViewModel.CustomerNo;
customer.Description = addCustomerViewModel.Description;
...
// 20 more attribute too...
return customer;
}
But Customers class have too many variable (customerNo, CustomerName, ...) , So this is the not good way.
When I use DbContextGenerator and Add classes to dataAnnotations and then When I udated the model, dataAnnotations is deleted. (Because DbContext classes are updated, too)
How Can I use ViewModels with DataAnnotations. And effective insert operation to Db? Article, Tutorial, example or advice?
I hope I can explain.
Thanks a lot...

You may take a look at AutoMapper which will simplify the mapping logic between your domain models and view models so that you don't need to manually map each property. Other than that there's nothing wrong with your code. You are already using a view model and have a mapping layer. So your GetCustomerFromViewModel function might become:
private Customers GetCustomerFromViewModel(AddCustomerViewModel addCustomerViewModel)
{
return Mapper.Map<AddCustomerViewModel, Customers>(addCustomerViewModel);
}
or completely get rid of it and directly use the AutoMapper call in your controller action because this function no longer brings much value:
[HttpPost]
[Authorize(Roles = "Administrator")]
public ActionResult AddCustomer(AddCustomerViewModel addCustomerViewModel)
{
var customer = Mapper.Map<AddCustomerViewModel, Customers>(addCustomerViewModel);
entity.Customers.Add(customer);
entity.SaveChanges();
return View(addCustomerViewModel);
}

Related

Sorting and pagination with many search fields

I followed this tutorial and tried to implement sorting, filtering and pagination in my MVC application. Generally it's OK - it works, but I don't like code which is the result of this - it's terrible, problematic and complicating.
Here is my model:
public class ProductOccurence
{
[Key]
public int ProductOccurenceId { get; set; }
public int ProductId { get; set; }
public int ShopId { get; set; }
public decimal ProductPrice { get; set; }
public DateTime ProductBuyDate { get; set; }
public bool IsPromotional { get; set; }
public virtual Product Product { get; set; }
public virtual Shop Shop { get; set; }
}
and ProductSearchModel class (ViewModel only for fields to search):
public class ProductOccurenceSearchModel
{
public string Description { get; set; }
public string ShopName { get; set; }
public decimal? PriceFrom { get; set; }
public decimal? PriceTo { get; set; }
public DateTime? BuyDateFrom { get; set; }
public DateTime? BuyDateTo { get; set; }
public bool? IsPromotional { get; set; }
}
also declaration of controller function:
public ViewResult Index(string sortOrder, ProductOccurenceSearchModel searchModel, string currentFilterDescription, string currentFilterShopName, decimal? currentFilterPriceFrom, decimal? currentFilterPriceTo, DateTime? currentFilterBuyDateFrom, DateTime? currentFilterBuyDateTo, bool? currentFilterIsPromotional, int? page)
as you can see, there is a lot of variables - also I need separate ViewBag for each field. In every place I need to refer to each variable, what is inelegant, illegible - and makes unnecessary redundant code.
In my view it looks like this:
Link for sorting column:
#Html.ActionLink("ID", "Index", new
{
sortOrder = ViewBag.IDSortParm,
currentFilterDescription = ViewBag.CurrentFilterDescription,
currentFilterShopName = ViewBag.CurrentFilterShopName,
currentFilterPriceFrom = ViewBag.CurrentFilterPriceFrom,
currentFilterPriceTo = ViewBag.CurrentFilterPriceTo,
currentFilterBuyDateFrom = ViewBag.CurrentFilterBuyDateFrom,
currentFilterBuyDateTo = ViewBag.CurrentFilterBuyDateTo,
currentFilterIsPromotional = ViewBag.CurrentFilterIsPromotional
})
and PagedList helper:
#Html.PagedListPager(Model.ProductOccurences, page => Url.Action("Index", new
{
page,
sortOrder = ViewBag.CurrentSort,
currentFilterDescription = ViewBag.CurrentFilterDescription,
currentFilterShopName = ViewBag.CurrentFilterShopName,
currentFilterPriceFrom = ViewBag.CurrentFilterPriceFrom,
currentFilterPriceTo = ViewBag.CurrentFilterPriceTo,
currentFilterBuyDateFrom = ViewBag.CurrentFilterBuyDateFrom,
currentFilterBuyDateTo = ViewBag.CurrentFilterBuyDateTo,
currentFilterIsPromotional = ViewBag.CurrentFilterIsPromotional
}))
This is needed to store the results while paging and sorting (parameters are transfered by URL).
I was looking for solutions, I read the tutorials, but never found a good solution for many search fields (only for one or two).
How can I make it simple and clean? For example through the transfer of all the values in one object or something like this. Thanks in advance!

Requires a model item of type 'System.Collections.Generic.IEnumerable`1

I have a LINQ query in my controller that has a join which selects all records. I'm then passing the ReportCompletionStatus.AsEnumerable() model to my view. But I keep getting the fowlling exceptions..
The model item passed into the dictionary is of type 'System.Data.Entity.Infrastructure.DbQuery`1
but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1
I'm setting the model AsEnumerable() and my view is expecting #model IEnumerable so i'm still not sure why it's complaning...
Controller
var ReportCompletionStatus = from r in db.Report_Completion_Status
join rc in db.Report_Category
on r.Report_Category equals rc.ReportCategoryID
select new
{
r.Report_Num,
rc.ReportCategory,
r.Report_Sub_Category,
r.Report_Name,
r.Report_Owner,
r.Report_Link,
r.Report_Description,
r.Last_Published,
r.Previous_Published,
r.Published_By,
r.Previous_Published_By,
r.Last_Edited,
r.Edited_By
};
return View(ReportCompletionStatus.AsEnumerable());
Model
#model IEnumerable<WebReportingTool.Report_Completion_Status>
With your select new, you project to an anonymous type, not to an IEnumerable<WebReportingTool.Report_Completion_Status>
You need to create a ViewModel class (as your projection has data from both Report_Completion_Status and Report_Category) and use it for projection and for your View's model.
class
public class SomeViewModel {
public int ReportNum {get;set;}
public string ReportCategory {get;set;
//etc.
}
projection
select new SomeViewModel
{
ReportNum = r.Report_Num,
ReportCategory = rc.ReportCategory,
//etc.
};
view
#model IEnumerable<SomeViewModel>
By the way, the AsEnumerable is not necessary.
Here's how I got it to work.
Model
public class ReportCategoryListModel
{
public int Report_Num { get; set; }
public string ReportCategory { get; set; }
public string Report_Sub_Category { get; set; }
public string Report_Name { get; set; }
public string Report_Owner { get; set; }
public string Report_Link { get; set; }
public string Report_Description { get; set; }
public Nullable<System.DateTime> Last_Published { get; set; }
public Nullable<System.DateTime> Previous_Published { get; set; }
public Nullable<int> Published_By { get; set; }
public Nullable<int> Previous_Published_By { get; set; }
public Nullable<System.DateTime> Last_Edited { get; set; }
public Nullable<int> Edited_By { get; set; }
}
Controller
var ReportCompletionStatus = from r in db.Report_Completion_Status
join rc in db.Report_Category
on r.Report_Category equals rc.ReportCategoryID
select new ReportCategoryListModel
{
Report_Num = r.Report_Num,
ReportCategory = rc.ReportCategory,
Report_Sub_Category = r.Report_Sub_Category,
Report_Name = r.Report_Name,
Report_Owner = r.Report_Owner,
Report_Link = r.Report_Link,
Report_Description = r.Report_Description,
Last_Published = r.Last_Published,
Previous_Published= r.Previous_Published,
Published_By = r.Published_By,
Previous_Published_By = r.Previous_Published_By,
Last_Edited = r.Last_Edited,
Edited_By = r.Edited_By
};
return View(ReportCompletionStatus);
View
#model IEnumerable<WebReportingTool.Models.ReportCategoryListModel>

Need help trying to create a one-to-many relationship using EF7, Asp.NET and SQLite

I am new to Entity Framework and Asp.NET, and therefore, struggling with creating database relationships within the Entity Framework.
I have two SQLite tables (Ticket and User) and have setup my entity models as follows:
public class Users
{
[ForeignKey("id")]
public int id { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public string email { get; set; }
public virtual ICollection<Tickets> Tickets { get; set; }
}
public class Tickets
{
public int id { get; set; }
public string summary { get; set; }
public string description { get; set; }
public string c_location { get; set; }
public string c_store_device { get; set; }
public string category { get; set; }
public DateTime? created_at { get; set; }
public DateTime? closed_at { get; set; }
public int priority { get; set; }
public int? assigned_to { get; set; }
public DateTime? due_at { get; set; }
public DateTime? updated_at { get; set; }
public string status { get; set; }
public virtual Users Users { get; set; }
}
I am trying to use Entity Framework 7 to export an IEnumerable<Tickets> that includes the User assigned to each Ticket.
I have tried to create my model relationship in MyDBContext as a single User can have multiple Tickets, and also has a foreign key associated in my Sqlite database (Tickets.assigned_to = User.id):
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Users - > many Tickets
modelBuilder.Entity<Users>()
.HasMany(p => p.Tickets)
.WithOne(e => e.Users)
.HasForeignKey(p => p.assigned_to);
}
My result ends up with Ticket data being exported, but against every ticket I see a null value for User:
[{"id":10002,...,"Users":null}]
When I use .Include() within my Repository to include each User like this:
public IEnumerable<Tickets> GetAll()
{
return _db.Tickets.Include(t => t.Users).ToList();
}
It results in the error
HTTP Error 502.3 - Bad Gateway
The specified CGI application encountered an error and the server terminated the process.
What I'm trying to retrieve is data that looks like:
{"Ticket";[{"id":10002,..."status":"closed"}],"Users":[{"id":"1"..."email":"johndoe#someplace.com"}]}
I know it probably has something to do with my relationship model, but I cannot work out what I am doing wrong.
First you should really derive your Users from IdentityUser. It helps when trying to wire up the relationship, but I will give you the answer based on your current models. Your ForeignKey property should be on the child entity. By naming conventions, which is what EF uses by default, your public Users Users works better if you put a public int UsersId. Then essentially what EF will do is from your public Users Users it will go to the Users table. Then it looks for the ForeignKey which is set to Id, so now we are in the Users Table looking at the id property. Then it looks for the naming convention UsersId and if it sees it, it will set that property to the value that it saw from the Users Table Id column.
Try using this
public class Users
{
public int id { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public string email { get; set; }
public virtual ICollection<Tickets> Tickets { get; set; }
}
public class Tickets
{
public int id { get; set; }
public string summary { get; set; }
public string description { get; set; }
public string c_location { get; set; }
public string c_store_device { get; set; }
public string category { get; set; }
public DateTime? created_at { get; set; }
public DateTime? closed_at { get; set; }
public int priority { get; set; }
public DateTime? due_at { get; set; }
public DateTime? updated_at { get; set; }
public string status { get; set; }
[ForeignKey("Id")]
public int UsersId { get; set; }
public virtual Users Users { get; set; }
}
and for your Fluent API configuring
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Users - > many Tickets
modelBuilder.Entity<Users>()
.HasMany(p => p.Tickets)
.WithOne();
}
Now all that does is create the relationship. In order to view the specific items you want to view, use a ViewModel. So, pull the two lists you want from where you want. Then use logic to separate the list how you want them to display.
public class UsersViewModel()
{
public UsersViewModel(Users user, List<Tickets> tickets)
{
this.first_name = user.first_name;
this.last_name = user.last_name;
this.email = user.email;
this.Tickets = new List<Tickets>();
foreach(var ticket in tickets)
{
if(ticket.UserId == user.Id)
{
this.Tickets.Add(ticket)
}
}
}
public string first_name { get; set; }
public string last_name { get; set; }
public string email { get; set; }
public List<Tickets> Tickets { get; set;}
}
then in your controller make your list
public IActionResult Index()
{
var usersList = _repository.Users.ToList();
var ticketsList = _repository.Tickets.ToList();
var model = new List<UsersViewModel>();
foreach(var user in usersList)
{
var listItem = new UsersViewModel(user, ticketsList);
model.Add(listItem);
}
return View(model);
}
or use a Linq query
public IActionResult Index()
{
var usersList = _repository.Users.ToList();
var model = new List<UsersViewModel>();
foreach(var user in usersList)
{
var ticketsList = from x in _repository.Tickets where x.UserId.Equals(user.Id) select x;
var listItem = new UsersViewModel(user, ticketsList);
model.Add(listItem);
}
return View(model);
}
then at the top of your view you should have
#model IEnumerable<UsersViewModel>

Get field from different model in MVC 5

I have a model linked to a second table:
public class Rock
{
public int ID { get; set; }
[ForeignKey("Con")]
public int ConID { get; set; }
public virtual Con Con { get; set; }
}
public class Con
{
public int ID { get; set; }
public virtual ICollection<Rock> Rock{ get; set; }
[Required]
[RegularExpression(#"^[0-9A-Za-z '']+$")]
[StringLength(200, MinimumLength = 3)]
public string Name { get; set; }
}
In my control, I have a 'create' action:
// GET: Rock/Create/3337
[Route("Rock/Create/{ConID?}")]
public ActionResult Create(int? ConID)
{
var rock= new Rock();
rock.ConID = (int)ConID;
return View(rock);
}
I'd like to get the con name from that table and send it to the view. At this point it doesn't know the name because there's no 'rock' record linking it yet.
Any suggestions? Thanks in advance!
If you have created a strongly typed view with Rock as Type, you need to either add 'Con Name' property to Rock Type or else you need to create a new Type and add the data to this Type which you want to pass to the View.
public class NewType
{
public int ConID { get; set; }
public string ConName { get; set; }
}
Add the data you want to pass in this Type and return view with this object:-
public ActionResult Create(int? ConID)
{
var newType= new NewType();
newType.ConID = (int)ConID;
newType.ConName = "XYZ";
return View(newType);
}
The two suggestions I would give are to pass Con.name to the view in the viewbag or to create a viewmodel, as Rahul suggested, that combines the properties of different classes that are needed for that particular view.

Why Web Api include properties in response that are not projected

I have a view model
public class MyViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Position { get; set; }
public string Email { get; set; }
public string Office { get; set; }
public DateTime? StartDate { get; set; }
public int? Age { get; set; }
public int? Salary { get; set; }
public int? Extn { get; set; }
}
And I am doing projection on my entity
public List<ViewModel.StaffViewModel> GetAll()
{
var context = new GistDemoDbEntities();
var model = context.Staff
.Select(s => new ViewModel.StaffViewModel
{
FirstName = s.FirstName,
LastName = s.LastName,
Position = s.Position,
Salary = s.Salary
}).ToList();
return model;
}
And use Web Api to return back as json, but in reponse I found out it includes other properties as well that define in the View Model with vlaue null. I only want to have those properties that I need in reponse, how is it possible?
You can either:
Remove them from your ViewModel, a view model should only conta9in
what you intend to use anyway.
or
Use [JsonIgnore] on your properties to prevent JSON.Net from mapping
them.
Json Ignore is an attribute, look here;
http://james.newtonking.com/json/help/index.html?topic=html/SerializationAttributes.htm

Resources