I'm working on my first WPF MVVM application. I created a database and wrote queries to fetch album names and song names. Now I want to fill a list in my View with the album names and a second list with corresponding songs. I'm new to c# and WPF. I'd like to know how a view model would like for my controller would look like.
My controller:
public class BandManagerController
{
private bandkramEntities _context;
public BandManagerController()
{
_context = new bandkramEntities();
}
public List<AlbumData> GetAlbumList()
{
return _context.albums
.Select(a => new AlbumData
{
AlbumID = a.AlbumID,
AlbumName = a.AlbumName,
})
.ToList();
}
public List<SongData> GetSongList(int albumID)
{
return _context.songs
.Where(s => s.AlbumID == albumID)
.Select(s => new SongData
{
SongID = s.SongID,
SongName = s.SongName
})
.ToList();
}
}
I created a helper class with the NotifyOfPropertyChange class and a song and album data class:
AlbumData.cs
public class AlbumData
{
public string AlbumName { get; set; }
public int AlbumID { get; set; }
}
SongData.cs
public class SongData
{
public string SongName { get; set; }
public int SongID { get; set; }
}
For a better overview I want to split my Viewmodel into 4 main parts.
SongViewModel.cs
public class SongViewModel : NotifyOfPropertyChange
{
public SongViewModel()
{
}
public string SongName { get; set; }
public int SongID { get; set; }
}
AlbumViewModel.cs
public class AlbumViewModel : NotifyOfPropertyChange
{
public AlbumViewModel()
{
}
public string AlbumName { get; set; }
public int AlbumID { get; set; }
}
SongListViewModel.cs
AlbumListViewModel.cs
I would like to know how 3. and 4. would have to look like to fill the album list with the album names and show corresponding songs in a second list.
With MVVM you would lose the controller and just have a ViewModel. There you would have your lists as such(including a notify property changed)
private ObservableCollection<SongData> _songList;
public ObservableCollection<SongData> SongList
{
get { return _songList; }
set { SetProperty(ref _songList, value, () => SongList); }
}
Then you would load this list at some point
public void LoadSongData(int albumID)
{
Using(YourContext _context = new YourContext)
{
SongList = new ObservableCollection(_context.songs
.Where(s => s.AlbumID == albumID)
.Select(s => new SongData
{
SongID = s.SongID,
SongName = s.SongName
})
.ToList());
}
}
Related
I have a view Model:
public class CountViewModel
{
public int NewsLetterEmailCount { get; set; }
public int CurrentMonthNewsLetter { get; set; }
public int NewsLetterPercentage { get; set; }
}
then I create the second ViewModel that want to list of title:
public class AdminDashboardUnreadMessage
{
[Display(Name = "عنوان")]
public string Title { get; set; }
//public List<AdminDashboardUnreadMessage>
AdminDashboardUnreadMessages { get; set; }
}
I have a ticket Table, these are my 2 services:
public async Task<CountViewModel> AdminContentCount()
{
CountViewModel adminContetnCount = new CountViewModel()
{
UserCount = await _userRepository.UserCount(),
CurrentMonthUser = await _userRepository.CurrentMonthUser(),
NewsLetterEmailCount = await _newsLetterRepository.NewsLetterEmailCount(),
CurrentMonthNewsLetter = await _newsLetterRepository.CurrentMonthNewsLetterEmail(),
return adminContetnCount;
}
public async Task<List<AdminDashboardUnreadMessage>> ShowUnReadTicketMessages()
{
return await _userRepository.ShowUnReadTicketMessages();
}
How can I use 2 services on my service
I mean I don't want to use from View Data
I think you need to define a new ViewModel like this :
public class NewViewModel
{
public CountViewModel CountModel { get; set; }
public AdminDashboardUnreadMessage AdminDashboardModel { get; set; }
}
And now you need to return this object as a result of a newly created service which responsible to aggregate the result of two different services.
public async Task<NewViewModel> NewService()
{
var adminContetnCount = await AdminContentCount();
var dashboard = await ShowUnReadTicketMessages();
return new NewViewModel
{
CountModel = adminContetnCount,
AdminDashboardModel= dashboard
};
}
I have parent and childs nested tables.
Here is my model:
public class Categories
{
[Key]
public int CategoriesId { get; set; }
public int Order { get; set; }
public string CategoryName { get; set; }
public List<News> News { get; set; }
}
public class News
{
[Key]
public int NewsId { get; set; }
public int CategoriesId { get; set; }
public string Content { get; set; }
public DateTime Date { get; set; }
...
public List<Comments> Comments { get; set; }
public Categories Categories { get; set; }
}
public class Comments
{
[Key]
public int CommentsId { get; set; }
public int NewsId { get; set; }
public string Comment { get; set; }
...
public News News { get; set; }
}
public class NewsImages
{
[Key]
public int ImageId { get; set; }
public int NewsId { get; set; }
public string ImageUrl { get; set; }
public bool Cover { get; set;}
...
public News News { get; set; }
}
I'm trying to send it from ViewComponent to View;
public async Task<IViewComponentResult> InvokeAsync()
{
var group = _dbContext.Categories.Where(k => k.Order != 0).OrderBy(h => h.Order)
.Select(c => new
{
C = c,
N = c.News.OrderByDescending(n => n.Date).Take(5)
.Select(r => new
{
Y = r.Comments,
R = r.NewsImages.Where(rs => rs.Cover == true).FirstOrDefault()
})
});
var model = group
.Select(m => m.C);
return View(await model.ToListAsync()) ;
}
I am sure there are enough News records for every Category, But I get error :
ArgumentNullException: Value cannot be null. (Parameter 'source')
AspNetCore.Views_Shared_Components_IndexKategori_Default.ExecuteAsync() in Default.cshtml
var bp = k.News.FirstOrDefault();
if I use that code works fine :
var model = _dbContext.Categories
.Include(h => h.News).ThenInclude(h => h.Comments)
.Include(h => h.News).ThenInclude(h => h.NewsImages)
.Where(h => h.Order != 0)
.OrderBy(h => h.Order)
But when I use the code above, a few records appear for some categories, and some categories react as if there are no records.
Where am I making mistakes?
Thank you in advance for those who helped ..
Whenever you have a big LINQ statement that throws an exception, and you can't find where the exception comes form, translate the LINQ into smaller steps, and ToList() every step.
public async Task<IViewComponentResult> InvokeAsync()
{
// Temp code: small steps, ToList after every step
var a = dbContext.Categories.Where(category => category.Order != 0).ToList();
var b = a.OrderBy(category => category.Order).ToList();
var c = b.Select(category => new
{
Category = category,
News = category.News.OrderByDescending(news => news.Date)
.Take(5)
.ToList();
})
.ToList();
var d = c.Select(item => new
{
Category = item.Category,
NewsItems = item.News.Select(news => new
{
Comments = news.Comments,
Images = news.NewsImages.Where(newsImage => newsImage.Cover).ToList(),
})
.ToList(),
})
.ToList();
var e = d.Select(item => new
{
Category = item.Category,
NewsItems = item.NewsItems.Select(newsItem => new
{
Comments = newsItem.Comments,
Images = images.FirstOrDefault();
})
.ToList(),
})
.ToList();
// original code:
var group = _dbContext.Categories.Where...
}
I'm sure that your debugger will tell you which step is incorrect.
So I have the following setup:
PLANNING:
public class Planning : ViewModelBase
{
public Planning()
{
AddNewActivityCommand = new RelayCommand(AddActivity, CanAddActivity);
}
public ObservableCollection<PlanningItem> PlanningItems { get; set; }
public PlanningItem SelectedPlan { get; set; }
#region AddNewActivity
public RelayCommand AddNewActivityCommand { get; private set; }
private bool CanAddActivity()
{
if (!PlanningItems.Any())
{
return true;
}
if (string.IsNullOrEmpty(PlanningItems[PlanningItems.Count - 1].Activities) != true ||
PlanningItems[PlanningItems.Count - 1].DhpRepresentativeSelected != null)
{
return true;
}
return false;
}
private void AddActivity()
{
PlanningItems.Add(new PlanningItem());
AddNewActivityCommand.RaiseCanExecuteChanged();
}
#endregion
}
PLANNING ITEM:
public class PlanningItem : ViewModelBase
{
private string _activity;
public ObservableCollection<OutreachUser> DhpRepresentativeSource
{
get
{
var userSource = new ObservableCollection<OutreachUser>();
using (var context = new Outreach_Entities())
{
var query = from a in context.UserInfoes
join b in context.PersonalInfoes on a.UserIdentity equals b.PersonIdentity
join c in context.PersonalTitles on b.TitleLink equals c.TitleIdentity into cGroup
from c in cGroup.DefaultIfEmpty()
select new OutreachUser
{
PersonLink = a.UserIdentity,
Username = a.Username,
FirstName = b.FirstName,
MiddleInitial = b.MiddleInitial,
LastName = b.LastName
};
foreach (var result in query)
{
userSource.Add(result);
}
return userSource;
}
}
}
public OutreachUser DhpRepresentativeSelected { get; set; }
public DateTime PlanningDate { get; set; }
public TimeSpan PlanningStart { get; set; }
public TimeSpan PlanningEnd { get; set; }
public int PlanningTotalHours { get; set; }
public string Activities
{
get
{
return _activity;
}
set
{
_activity = value;
RaisePropertyChanged(nameof(Activities), "", _activity, true);
}
}
}
I have a ListBox bound to the PlanningItems Observable Collection.
I want to be able to add a new item to the list if the following criteria are met:
The Planning Items Collection is empty.
The last item in the Planning Items Collection has a DhpRepresentativeSelected that is not null.
The last item in the Planning Items Collection has some text in the Activities string.
The first item is easy enough because I call AddNewActivityCommand.RaiseCanExecuteChanged(); after I add a new item from an empty list.
Now I need to call the AddNewActivityCommand.RaiseCanExecuteChanged(); from within the PlanningItem ViewModel, but it does not have access rights to the command.
Clueless pointed me to the answer.
What I did was inside of my Planning ViewModel I created an internal Method that called the AddNewActivityCommand.RaiseCanExecuteChanged() method. I think called that method from within the PlanningItems ViewModel.
I'm trying to figure out how to make an object data source where I can select which columns to display in Visual Studio and all that. Here's what I have so far, but I'm not sure what else I'm supposed to do?
public class ItemData
{
public string ItemName { get; set; }
public string Description { get; set; }
public string Quantity { get; set; }
public string ManuPartNumber { get; set; }
public string ListID { get; set; }
public string VendorRef { get; set; }
public string VendorName { get; set; }
public string EditSequence { get; set; }
public string UPC { get; set; }
}
public class ItemDataSource : IEnumerable<ItemData>
{
private ICollection<ItemData> list;
public ItemDataSource()
{
try
{
list = QBCom.GetItemList();
}
catch (Exception e)
{
list = new List<ItemData>();
}
}
public ItemDataSource(IEnumerable<ItemData> data)
{
list = data.ToList();
}
public IEnumerator<ItemData> GetEnumerator()
{
foreach (var item in list)
{
yield return item;
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return ((IEnumerable<ItemData>)this).GetEnumerator();
}
}
I am not talking about ASP.net, I'm talking about stuff like DataTables and so forth. Just a regular winforms program.
I assume you want to bind to a DataGridView if so I would inherit from BindingSource and set the list to the DataSource. You can then set the datagridview DataSource to the object to view the columns in the visual studio properties window.
public class ItemDataSource : BindingSource
{
private ICollection<ItemData> list;
public ItemDataSource()
{
try
{
list = QBCom.GetItemList();
}
catch (Exception e)
{
list = new List<ItemData>();
}
this.DataSource = list;
}
public ItemDataSource(IEnumerable<ItemData> data)
{
list = data.ToList();
this.DataSource = list;
}
}
In order to create a more elegant solution I'm curios to know your suggestion about a solution to persist a collection.
I've a collection stored on DB.
This collection go to a webpage in a viewmodel.
When the go back from the webpage to the controller I need to persist the modified collection to the same DB.
The simple solution is to delete the stored collection and recreate all rows.
I need a more elegant solution to mix the collections and delete not present record, update similar records ad insert new rows.
this is my Models and ViewModels.
public class CustomerModel
{
public virtual string Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<PreferredAirportModel> PreferedAirports { get; set; }
}
public class AirportModel
{
public virtual string Id { get; set; }
public virtual string AirportName { get; set; }
}
public class PreferredAirportModel
{
public virtual AirportModel Airport { get; set; }
public virtual int CheckInMinutes { get; set; }
}
// ViewModels
public class CustomerViewModel
{
[Required]
public virtual string Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<PreferredAirporViewtModel> PreferedAirports { get; set; }
}
public class PreferredAirporViewtModel
{
[Required]
public virtual string AirportId { get; set; }
[Required]
public virtual int CheckInMinutes { get; set; }
}
And this is the controller with not elegant solution.
public class CustomerController
{
public ActionResult Save(string id, CustomerViewModel viewModel)
{
var session = SessionFactory.CurrentSession;
var customer = session.Query<CustomerModel>().SingleOrDefault(el => el.Id == id);
customer.Name = viewModel.Name;
// How can I Merge collections handling delete, update and inserts ?
var modifiedPreferedAirports = new List<PreferredAirportModel>();
var modifiedPreferedAirportsVm = new List<PreferredAirporViewtModel>();
// Update every common Airport
foreach (var airport in viewModel.PreferedAirports)
{
foreach (var custPa in customer.PreferedAirports)
{
if (custPa.Airport.Id == airport.AirportId)
{
modifiedPreferedAirports.Add(custPa);
modifiedPreferedAirportsVm.Add(airport);
custPa.CheckInMinutes = airport.CheckInMinutes;
}
}
}
// Remove common airports from ViewModel
modifiedPreferedAirportsVm.ForEach(el => viewModel.PreferedAirports.Remove(el));
// Remove deleted airports from model
var toDelete = customer.PreferedAirports.Except(modifiedPreferedAirports);
toDelete.ForEach(el => customer.PreferedAirports.Remove(el));
// Add new Airports
var toAdd = viewModel.PreferedAirports.Select(el => new PreferredAirportModel
{
Airport =
session.Query<AirportModel>().
SingleOrDefault(a => a.Id == el.AirportId),
CheckInMinutes = el.CheckInMinutes
});
toAdd.ForEach(el => customer.PreferedAirports.Add(el));
session.Save(customer);
return View();
}
}
My environment is ASP.NET MVC 4, nHibernate, Automapper, SQL Server.
Well, if "elegant" is just "don't clear and recreate all" (untested) :
var airports = customer.PreferedAirports;
var viewModelAirports = viewModel.PreferredAirports;
foreach (var airport in airports) {
//modify common airports
var viewModelAirport = viewModelAirports.FirstOrDefault(m => m.AirportId == airport.AirportId);
if (viewModelAirport != null) {
airport.X = viewModelAirport.X;
airport.Z = viewModelAirport.Z;
//remove commonAirports from List
viewModelAirports.Remove(viewModelAirport);
continue;
}
//delete airports not present in ViewModel
customer.PreferedAirports.Remove(airport);
}
//add new airports
foreach (var viewModelAirport in viewModelAirports) {
customer.PreferedAirports.Add(new PreferredAirportModel {
Airport = session.Query<AirportModel>().SingleOrDefault(a => a.Id == el.AirportId),
CheckInMinutes = el.CheckInMinutes
});
}
session.Save(customer);