Sorting and pagination with many search fields - asp.net-mvc

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!

Related

CS0411: the type arguments for method cannot be inferred from the usage mvc

I have looking on SO but I didn't find a solution and I'm totally lost the correct way to fix this.
What's happening?
I'm getting error CS0411 in a view: the type arguments for method cannot be inferred from the usage mvc
I'm using a Tuple in that view to use 2 models.
var model = new Tuple<IList<TimeTableInsertModel>, List<Ticket>>(ttc.getTimeTableDetails(id), ticket);
#model Tuple<IList<TimeTableInsertModel>, List<Ticket>>
And I want to use the field TicketQuantity.
#Html.HiddenFor(item.TicketQuantity)
But that's give the error. I can use the property in the view without any #Html functions.
The views:
public class TimeTableInsertModel
{
[Key]
public int TimeTableId { get; set; }
public int MovieId { get; set; }
public int RoomId { get; set; }
public int SeatsAvaible { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public virtual Movie Movie { get; set; }
public virtual ICollection<Reservation> Reservations { get; set; }
public virtual Room Room { get; set; }
public int TicketQuantity { get; set; }
}
public partial class Ticket
{
public Ticket()
{
ReservationTickets = new HashSet<ReservationTicket>();
}
public int TicketId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public virtual ICollection<ReservationTicket> ReservationTickets { get; set; }
}
I'm assuming for the moment you're looping through an IEnumerable like this:
#foreach (var item in Model.Item1)
{
...
Html.HiddenFor(item.TicketQuantity);
...
}
The HiddenFor method takes a delegate as an argument, so instead of this:
#Html.HiddenFor(item.TicketQuantity)
you need to do something like this:
#Html.HiddenFor( m = > item.TicketQuantity)

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

Model count = null

I'm rewriting this question:
I have 2 models. Entry and Topic.
public class Entry
{
public int EntryId { get; set; }
public int UserId { get; set; }
public int TopicId { get; set; }
public String EntryQuestion { get; set; }
public String EntryAnswer { get; set; }
public int EntryReview { get; set; }
public String QuestionValidationURL { get; set; }
public virtual ICollection<Topic> TopicList { get; set; }
}
public class Topic
{
public int TopicId { get; set; }
public String TopicName { get; set; }
}
I followed an example on ASP.Net/MVC to set up my models this way.
What I would like to do is for every entry item I have a TopicId, but then I'd like to convert that to a TopicName by accessing my TopicList.
My question is, how do I load TopicList?
In the examples I'm following I'm seeing something about LazyLoading and EagerLoading, but it doesn't seem to be working.
I tried doing the following from my Entry controller:
db.Entries.Include(x => x.TopicList).Load();
But that still gives me a TopicList of 0 (which is better than null)
How can I do this?
In my view I'm binding to the Entries like this:
#model IEnumerable<projectInterview.Models.Entry>
I would like to access the TopicList here:
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.TopicId)
</td>
...
</tr>
I'd like to use the TopicId in this loop and display the TopicName that is part of the object in the collection.
I'm assuming you're following an Entity Framework example. You're trying to create a one-to-many relationship, as far as I can tell, although I'm unsure about which end is which.
In the general case, to establish a one-to-many relationship, you have to do something like this:
public class One
{
[Key]
public int Id { get; set; }
public virtual ICollection<Many> Many { get; set; }
}
public class Many
{
[Key]
public int Id { get; set; }
[ForeignKey("One")]
public int OneId { get; set; }
public virtual One One { get; set; }
}
If what you're trying to do is have one Entry relating to many Topic objects, then you're almost there but you're lacking something.
For the ICollection<Topic> to actually contain anything, the (many) Topic objects need to have a foreign key to the (one) Entry. (It also doesn't hurt to explicitly mark the primary key on both sides, rather than relying on the EF conventions.)
public class Topic
{
[Key]
public int TopicId { get; set; }
public String TopicName { get; set; }
[ForeignKey("Entry")]
public int EntryId { get; set; }
public virtual Entry Entry { get; set; }
}
public class Entry
{
[Key]
public int EntryId { get; set; }
public int UserId { get; set; }
public int TopicId { get; set; }
public String EntryQuestion { get; set; }
public String EntryAnswer { get; set; }
public int EntryReview { get; set; }
public String QuestionValidationURL { get; set; }
public virtual ICollection<Topic> TopicList { get; set; }
}
Now TopicList should be an actual and populated collection, without the need to do an Include.
If, on the other hand, you want one Topic relating to many Entry objects, then you have it a little backwards. The correct way would be:
public class Topic
{
[Key]
public int TopicId { get; set; }
public String TopicName { get; set; }
public virtual ICollection <Entry> Entries { get; set; }
}
public class Entry
{
[Key]
public int EntryId { get; set; }
public int UserId { get; set; }
public String EntryQuestion { get; set; }
public String EntryAnswer { get; set; }
public int EntryReview { get; set; }
public String QuestionValidationURL { get; set; }
[ForeignKey("Topic")]
public int TopicId { get; set; }
public virtual Topic Topic { get; set; }
}
In this case, you may or may not use db.Entries.Include(x => x.Topic) depending on whether you want them loaded all at once or one-by-one on demand. Regardless of what you choose, the following expression should return the proper value:
myEntry.Topic.TopicName
If I understand you correctly you have added the list of Topics to the Entry just to get the name of the topic when displaying the entry. The best way to do this is to actually have a Topic property in your entry model. So your model would look like this:
public class Entry
{
public int EntryId { get; set; }
public int UserId { get; set; }
public int TopicId { get; set; }
public String EntryQuestion { get; set; }
public String EntryAnswer { get; set; }
public int EntryReview { get; set; }
public String QuestionValidationURL { get; set; }
//Change this.....
public virtual Topic Topic { get; set; }
}
Then in your view you would use (assuming the Model is an IEnumerable):
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => modelItem.Topic.TopicName )
</td>
...
</tr>
This link has a great example of how to do this:
http://weblogs.asp.net/manavi/archive/2011/03/28/associations-in-ef-4-1-code-first-part-2-complex-types.aspx
In my opinion problem is with casting. In view you have IEnumerable<projectInterview.Models.Entry> while Topics is ICollection<Topic>, which is a collection of different type
Topics = null means there are no Topics in the list to iterate over. How do you fill them? Your view expects IEnumerable how do you cast your topics to the entries?
Based on the original question I've added a small working example, maybe it helps you to find your bug.
Controller:
public class TestController : Controller
{
public ActionResult Index()
{
var viewModel = new ViewModel()
{
Topics = new List<Topic>()
};
viewModel.Topics.Add(new Topic() { header = "test" });
viewModel.Topics.Add(new Topic() { header = "test2" });
return View(viewModel);
}
}
Model:
public class ViewModel
{
public virtual ICollection<Topic> Topics { get; set; }
public int getCount()
{
return Topics.Count;
}
}
public class Topic
{
public string header { get; set; }
}
View:
#model testProject.Models.ViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#Model.getCount()
#foreach(var item in Model.Topics)
{
<div>#item.header</div>
}
Output:
Index
2
test
test2
It seems that you are not initializing your Topics anywhere in the code. If the collection is null it means it is not initialized. If you instantiate it with
ICollection<Topic> Topics = new List<Topic>();
Once initialized you should receive zero when calling Topics.Count. If you do not make a call to a database it will stay zero.
In your case check whether you are instantiating the Topics.

ASP.NET MVC What is the best way to use ViewModels?

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);
}

PagedList in MVC3 with IQueryable

I can't understand what i'm doing wrong. Every time I'm getting this error:
The entity or complex type 'BusinessLogic.CompanyWithDivisionCount' cannot be constructed in a LINQ to Entities query.
I need to get info from 'Company' table and divisions count of each company from 'Division' table, and then make PagedList. Here is my 'Company' table:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using BusinessLogic.Services;
using BusinessLogic.Models.ValidationAttributes;
namespace BusinessLogic.Models
{
public class Company
{
public Company()
{
Country = "US";
Status = true;
}
public int Id { get; set; }
[Required]
[UniqueCompanyName]
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public int Zip { get; set; }
public string Country { get; set; }
public string ContactInfo { get; set; }
[Required]
public DateTime EffectiveDate { get; set; }
public DateTime TerminationDate { get; set; }
public bool Status { get; set; }
[Required]
public string URL { get; set; }
public string EAP { get; set; }
public string EAPCredentials { get; set; }
public string BrandingColors { get; set; }
public string Comments { get; set; }
}
}
Here is my domain model:
public class Company
{
public Company()
{
Country = "US";
Status = true;
}
public int Id { get; set; }
[Required]
[UniqueCompanyName]
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public int Zip { get; set; }
public string Country { get; set; }
public string ContactInfo { get; set; }
[Required]
public DateTime EffectiveDate { get; set; }
public DateTime TerminationDate { get; set; }
public bool Status { get; set; }
[Required]
public string URL { get; set; }
public string EAP { get; set; }
public string EAPCredentials { get; set; }
public string BrandingColors { get; set; }
public string Comments { get; set; }
}
public class CompanyWithDivisionCount: Company // I'm using this
{
public int DivisionCount { get; set; }
}
Here is my controller:
public ActionResult CompaniesList(int? page)
{
var pageNumber = page ?? 1;
var companies = companyService.GetCompaniesWithDivisionsCount2();
var model = companies.ToPagedList(pageNumber, PageSize);
return View(model);
}
And here is my service part:
public IQueryable<CompanyWithDivisionCount> GetCompaniesWithDivisionsCount2()
{
return (from c in dataContext.Companies.AsQueryable()
select new CompanyWithDivisionCount
{
Id = c.Id,
Name = c.Name,
Status = c.Status,
EffectiveDate = c.EffectiveDate,
URL = c.URL,
EAP = c.EAP,
EAPCredentials = c.EAPCredentials,
Comments = c.Comments,
DivisionCount = (int)dataContext.Divisions.Where(b => b.CompanyName == c.Name).Count()
});
}
}
Thanks for help!!!
Creator of PagedList here. This has nothing to do with PagedList, but rather is an Entity Framework issue (I'm no expert on Entity Framework, so can't help you there). To confirm that this is true, write a unit test along the following lines:
[Test]
public void ShouldNotThrowAnException()
{
//arrange
var companies = companyService.GetCompaniesWithDivisionsCount2();
//act
var result = companies.ToList();
//assert
//if this line is reached, we win! no exception on call to .ToList()
}
I would consider changing you data model if possible so that instead of relating Companies to Divisions by name strings, instead use a properly maintained foreign key relationship between the two objects (Divisions should contain a CompanyID foreign key). This has a number of benefits (including performance and data integrity) and will almost certainly make your life easier moving forward if you need to make further changes to you app (or if any company ever decides that it may re-brand it's name).
If you create a proper foreign key relationship then your domain model could look like
public class Company
{
...
public virtual ICollection<Division> Divisions{ get; set; }
public int DivisionCount
{
get
{
return this.Divisions.Count()
}
}
...
}

Resources