Razor component System.nullreferenceexception at GetFromJsonAsync - json

Student studying FullSD encountered an error whilst following lab that my teacher can't figure out.
Have a page where I can create an entry that can be displayed. But when I refresh to view the newly added entry, I get a loading error. When looking at it from Developer Tools, it calls System.nullreferenceexception. Despite this, the entry can still be viewed from datatable in Visual Studios
The error references a block of code starting at line 37 of my Index.razor for this page, which my lecturer narrowed down to being this:
protected async override Task OnInitializedAsync()
{
Bookings = await _client.GetFromJsonAsync<List<Booking>>($"{Endpoints.BookingsEndpoint}");
}
Entire Index.razor:
#page "/bookings/"
#inject HttpClient _client
#inject IJSRuntime js
#attribute [Authorize]
<h3 class="card-title">Car Bookings</h3>
<br />
<a href="/bookings/create" class="btn btn-secondary">
<span class="oi oi-plus"></span>
Create New Booking
</a>
<br />
<br />
#if (Bookings == null)
{
<div class="alert alert-info">Loading Bookings...</div>
}
else
{
<table class="table table-responsive">
<thead>
<tr>
<th>Booking Id</th>
<th>Customer License</th>
<th>Date</th>
<th>Duration in days</th>
<th>Plate Number</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
#foreach (var booking in Bookings)
{
<tr>
<td>#booking.Id</td>
<td>#booking.CustomerId</td>
<td>#booking.DateCreated.Date.ToString("dd/mm/yyyy")</td>
<td>#(booking.DateIn != nullDT ? (booking.DateIn - booking.DateOut).TotalDays.ToString() : "To Be Determined")</td>
<td>#booking.Vehicle.LicensePlateNumber</td>
<td>
<a href="/bookings/view/#booking.Id" class="btn btn-primary">
<span class="oi oi-book"></span>
</a>
<a href="/bookings/edit/#booking.Id" class="btn btn-warning">
<span class="oi oi-pencil"></span>
</a>
<button class="btn btn-danger" #onclick="#(()=>Delete(booking.Id))">
<span class="oi oi-delete"></span>
</button>
</td>
</tr>
}
</tbody>
</table>
}
#code {
private List<Booking> Bookings;
private DateTime nullDT = new DateTime(1, 1, 1, 0, 0, 0);
protected async override Task OnInitializedAsync()
{
Bookings = await _client.GetFromJsonAsync<List<Booking>>($"{Endpoints.BookingsEndpoint}");
}
async Task Delete(int bookingId)
{
var booking = Bookings.First(q => q.Id == bookingId);
var confirm = await js.InvokeAsync<bool>("confirm", $"Do you want to delete {booking.Id}?");
if (confirm)
{
await _client.DeleteAsync($"{Endpoints.BookingsEndpoint}/{bookingId}");
await OnInitializedAsync();
}
}
}
I expected the page to display the newly created entry as I have done with previously created pages in my Lab Workbook
Can anyone else provide some insight?
Edit: Here's my Booking class
using System;
namespace CarRentalManagement.Shared.Domain
{
public class Booking:BaseDomainModel
{
public DateTime DateOut { get; set; }
public DateTime DateIn { get; set; }
public int VehicleId { get; set; }
public virtual Vehicle Vehicle { get; set; }
public int CustomerId { get; set; }
public virtual Customer Customer { get; set; }
}
}

Related

PaginatedList onpostasync - ArgumentNullException: Value cannot be null. (Parameter 'model') when submitting on paginnatelist

HI Perhaps someone can point me in the right direction.
I created a paginated list page use the example on the ms site.
https://learn.microsoft.com/en-us/aspnet/core/data/ef-rp/sort-filter-page?view=aspnetcore-5.0
and modified it slightly. I replaced one item with a input box as would like to edit the list and do a bulk save instead on opening every item on a new page to edit it.
but when it click the submit button i get the error.
ArgumentNullException: Value cannot be null. (Parameter 'model')
Microsoft.AspNetCore.Mvc.RazorPages.PageModel.TryValidateModel(object model, string name)
if I bind the property
[BindProperty(SupportsGet = true)]
public PaginatedList<CompanyDataListing> CustomersDisplayList { get; set; }
i get the follow error
ArgumentException: Type 'BizFinder.PaginatedList`1[BizFinder.Data.CompanyDataListing]' does not have a default constructor (Parameter 'type')
and even the paginated list does not render.
My code for the sumbit is as follows.
public PaginatedList<CompanyDataListing> CustomersDisplayList { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!TryValidateModel(CustomersDisplayList, nameof(CustomersDisplayList)))
{
return Page();
}
foreach (var item in CustomersDisplayList)
{
if (item.GoogleCategory != "")
{
string cat = item.GoogleCategory;
}
}
return Page();
}
and my html is as follow.
#using (Html.BeginForm(FormMethod.Post,
new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="#Model.NameSort"
asp-route-currentFilter="#Model.CurrentFilter">
#Html.DisplayNameFor(model => model.CustomersDisplayList[0].CompanyName)
</a>
</th>
<th>
#Html.DisplayNameFor(model => model.CustomersDisplayList[0].Keywords)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="#Model.DateSort"
asp-route-currentFilter="#Model.CurrentFilter">
#Html.DisplayNameFor(model => model.CustomersDisplayList[0].GoogleCategory)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.CustomersDisplayList)
{
<tr>
<td>
<input type="hidden" asp-for="CustomersDisplayList[0].Id" />
#Html.DisplayFor(modelItem => item.CompanyName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Keywords)
</td>
<td>
#*#Html.DisplayFor(modelItem => item.GoogleCategory)*#
<input asp-for="CustomersDisplayList[0].GoogleCategory" name="Category1" placeholder="Input your keyword" class="form-control GoogleCategory" autofocus="autofocus" />
<span asp-validation-for="CustomersDisplayList[0].GoogleCategory" class="text-danger"></span>
</td>
<td>
<a asp-page="./Edit" asp-route-id="#item.Id">Edit</a> |
<a asp-page="./Details" asp-route-id="#item.Id">Details</a> |
<a asp-page="./Delete" asp-route-id="#item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
<div asp-validation-summary="All">
<span>Please correct the following errors</span>
</div>
#* #Html.ValidationSummary(true, "", new { #class = "text-danger" })*#
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</div>
}
and the full code base is
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BizFinder.Data;
using BizFinder.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
//https://learn.microsoft.com/en-us/aspnet/core/data/ef-rp/sort-filter-page?view=aspnetcore-5.0
namespace BizFinder.Pages.Admin
{
public class ManCatergoryModel : PageModel
{
private readonly ApplicationDbContext _context;
private readonly IConfiguration Configuration;
private readonly IAuthorizationService _authorizationService;
public int PageSize { get; set; } = 10;
// private readonly SchoolContext _context;
public ManCatergoryModel(ApplicationDbContext context, IConfiguration configuration)
{
_context = context;
Configuration = configuration;
}
public string NameSort { get; set; }
public string DateSort { get; set; }
public string CurrentFilter { get; set; }
public string CurrentSort { get; set; }
// public IList<CompanyDataListing> CustomersDisplayList { get; set; }
// [BindProperty(SupportsGet = true)]
public PaginatedList<CompanyDataListing> CustomersDisplayList { get; set; }
public async Task OnGetAsync(string sortOrder,
string currentFilter, string searchString, int? pageIndex)
{
CurrentSort = sortOrder;
NameSort = String.IsNullOrEmpty(sortOrder) ? "CompanyName" : "";
DateSort = sortOrder == "Keywords" ? "date_desc" : "Date";
if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}
CurrentFilter = searchString;
// using System;
IQueryable<CompanyDataListing> CompData = from s in _context.CompanyDataList
where s.GoogleCategory == null
select s;
switch (sortOrder)
{
case "name_desc":
CompData = CompData.OrderByDescending(s => s.CompanyName);
break;
case "Date":
CompData = CompData.OrderBy(s => s.Keywords);
break;
case "GoogleCategory":
CompData = CompData.OrderByDescending(s => s.GoogleCategory);
break;
default:
CompData = CompData.OrderBy(s => s.CompanyName);
break;
}
// var pageSize = Configuration.GetValue("PageSize", 4);
CustomersDisplayList = await PaginatedList<CompanyDataListing>.CreateAsync(
CompData.AsNoTracking(), pageIndex ?? 1, PageSize);
}
public async Task<IActionResult> OnPostAsync(PaginatedList<CompanyDataListing> Datalist)
{
// List < CompanyDataListing > mm = CustomersDisplayList.ToList();
foreach (var item in Datalist)
{
string str = item.GoogleCategory;
}
return Page();
}
}
}
The first thing you need to know is that the pagination in the tutorial is the back-end pagination. When you click the next page, the data is re-requested every time, so if you want to submit in batches, you can only submit current page data.
If you want the data of the current page to be submitted in batches, then you can modify your code as follows.
PageModel:
public class IndexModel : PageModel
{
private readonly PageListRazorContext _context;
private readonly IConfiguration Configuration;
public IndexModel(PageListRazorContext context, IConfiguration configuration)
{
_context = context;
Configuration = configuration;
}
public string NameSort { get; set; }
public string KeywordsSort { get; set; }
public string CurrentSort { get; set; }
public PaginatedList<CompanyDataListing> CompanyDataListing { get;set; }
public async Task OnGetAsync(string sortOrder,int? pageIndex)
{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
KeywordsSort = sortOrder == "Keywords" ? "keywords_desc" : "Keywords";
IQueryable<CompanyDataListing> company = from s in _context.CompanyDataListing
select s;
switch (sortOrder)
{
case "name_desc":
company = company.OrderByDescending(s => s.CompanyName);
break;
case "Keywords":
company = company.OrderBy(s => s.Keywords);
break;
case "keywords_desc":
company = company.OrderByDescending(s => s.Keywords);
break;
default:
company = company.OrderBy(s => s.CompanyName);
break;
}
var pageSize = Configuration.GetValue("PageSize", 4);
CompanyDataListing = await PaginatedList<CompanyDataListing>.CreateAsync(
company.AsNoTracking(), pageIndex ?? 1, pageSize);
}
public IActionResult OnPost(List<CompanyDataListing> Datalist)
{
//....
return RedirectToPage("./Index");
}
}
Page:
<form method="post">
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="#Model.NameSort">
#Html.DisplayNameFor(model => model.CompanyDataListing[0].CompanyName)
</a>
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="#Model.KeywordsSort">
#Html.DisplayNameFor(model => model.CompanyDataListing[0].Keywords)
</a>
</th>
<th>
#Html.DisplayNameFor(model => model.CompanyDataListing[0].GoogleCategory)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.CompanyDataListing)
{
<tr>
<td>
<input type="hidden" asp-for="#item.CompanyName" name="Datalist[#i].CompanyName" />
#Html.DisplayFor(modelItem => item.CompanyName)
</td>
<td>
<input type="hidden" asp-for="#item.Keywords" name="Datalist[#i].Keywords" />
#Html.DisplayFor(modelItem => item.Keywords)
</td>
<td>
<input asp-for="#item.GoogleCategory" name="Datalist[#i].GoogleCategory" placeholder="Input your keyword" class="form-control GoogleCategory" autofocus="autofocus" />
<span asp-validation-for="#item.GoogleCategory" class="text-danger"></span>
</td>
<td>
<a asp-page="./Edit" asp-route-id="#item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="#item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="#item.ID">Delete</a>
</td>
#{i++;}
</tr>
}
</tbody>
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</div>
#{
var prevDisabled = !Model.CompanyDataListing.HasPreviousPage ? "disabled" : "";
var nextDisabled = !Model.CompanyDataListing.HasNextPage ? "disabled" : "";
}
<a asp-page="./Index"
asp-route-sortOrder="#Model.CurrentSort"
asp-route-pageIndex="#(Model.CompanyDataListing.PageIndex - 1)"
class="btn btn-primary #prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="#Model.CurrentSort"
asp-route-pageIndex="#(Model.CompanyDataListing.PageIndex + 1)"
class="btn btn-primary #nextDisabled">
Next
</a>
</form>
Test result:
You are referencing CustomerDisplayList in the OnPost handler but it has not been initialised, hence the exception. You initialised it in the OnGet handler, but that only executes for GET requests. If you want to use a POST request, you also need to initialise the CustomerDisplayList in that too.
For what it's worth, the example you linked to uses GET requests for the whole cycle. This usually makes more sense for things like paginating and filtering results. That way, the criteria are passed in the URL as query string values or route data, which means that you can bookmark filtered/paged results. You won't need to render the anti forgery token for GET requests.

My table rows and columns disappear when I click delete button in Angular?

When I click the delete button to delete a row using delete UserById API, all my rows and columns disappear but when I refresh the page I get them back and the deleted row remains deleted(which is good) but I don't want my other data to disappear upon clicking delete and have to refresh to get them back. How do I fix this?
type script
export class SearchDeleteComponent implements OnInit {
public deleteUser(num:number){
let resp=this.service.deleteUser(num);
resp.subscribe((data)=>this.users=data);
}
html
<div class="container">
<div class="row col-md-6 col-md-offset-2 custyle">
<table class="table table-striped custab">
<thead>
<tr>
<th>Full Name</th>
<th>Date Of Birth</th>
<th>Gender</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of users">
<td>{{user.firstName+ ' '+ user.lastName}}</td>
<td>{{user.dob}}</td>
<td>{{user.gender}}</td>
<td class="text-center"><button class="btn btn-danger btn-xs" (click)="deleteUser(user.num)"><span class="glyphicon glyphicon-remove"></span> Delete</button></td>
</tr>
</tbody>
</table>
</div>
Delete API back-end*
#Stateless
public class ManageUserBean {
#PersistenceContext(unitName = "users")
private EntityManager entityManager;
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void deleteUser(int num) throws Exception {
try {
System.out.println("num : " + num);
Query q = entityManager.createQuery("SELECT u FROM User u where u.num = :num");
q.setParameter("num", num);
entityManager.remove(entityManager.merge(q.getSingleResult()));
entityManager.flush();
} catch (Exception e) {
System.out.println(e);
}
}
#Stateless
#Path("/{versionID}/example/")
public class ExampleAPI {
#EJB
ManageUserBean manageUserBean;
#Context
private UriInfo context;
#DELETE
#Path("/users/delete/{num}")
#Produces(MediaType.APPLICATION_JSON)
public void deleteMessage(#PathParam("num") int num) throws Exception {
manageUserBean.deleteUser(num);
}
I should add a return type and return the allUsers method in the delete function.
Edited the following code:
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public List<User> deleteUser(int num) throws Exception {
try {
System.out.println("num : " + num);
Query q = entityManager.createQuery("SELECT u FROM User u where u.num = :num");
q.setParameter("num", num);
entityManager.remove(entityManager.merge(q.getSingleResult()));
entityManager.flush();
return (List<User>) q.getResultList();
} catch (Exception e) {
System.out.println(e);
return null;
}
#DELETE
#Path("/users/delete/{num}")
#Produces(MediaType.APPLICATION_JSON)
public List<User> deleteMessage(#PathParam("num") int num) throws Exception {
manageUserBean.deleteUser(num);
return manageUserBean.allUserss();
}

Why is model data passed from view to controller null?

I have a view where I loop through the model list and display data. I am trying to pass that model to a different controller/action on link click. The data being passed is null. How do I do this?
View:
#Model TransactionViewModel
<table class="table table-striped table-hover visible-lg visible-md visible-sm " style="white-space:nowrap;">
<thead>
<tr>
<th>Date</th>
<th>Amount</th>
<th>Tag Number</th>
<th>Payment Method</th>
<th>Prior Balance</th>
<th>Current Balance</th>
<th>Description</th>
<th>Comments</th>
<th>Receipt</th>
</tr>
</thead>
<tbody>
#if (Model != null && Model.Transactions != null)
{
#foreach (var Tran in #Model.Transactions)
{
<tr>
<td>#Tran.TimeStamp</td>
<td>#Tran.Fare</td>
<td>#Tran.FullTagNum</td>
<td>#Tran.PaymentMethod</td>
<td>#Tran.PreviousBalance</td>
<td>#Tran.NewBalance</td>
<td>#Tran.PaymentDescription</td>
<td>#Tran.Comments</td>
#if (Tran.Processing_ref_string != null)
{
<td>
Receipt
</td> /*how do I pass in the dynamic variable Tran*/
}
else
{
<td>Not Available</td>
}
</tr>
}
}
</tbody>
</table>
Controller Action:
public async Task<IActionResult> PrintReceipt(ReplenishmentRecordResponse ReceiptData){
//data manipulation
}
Model:
public class TransactionViewModel
{
[Display(Name = "From", Prompt = "Starting Date")]
public DateTime StartDate { get; set; }
[Display(Name = "To", Prompt = "Ending Date")]
public DateTime EndDate { get; set; }
public List<ReplenishmentRecordResponse> Transactions { get; set; }
}
I would utilize asp-route-id and asp-page-handler TagHelpers to route your ID back to your PrintReceipt() method.
You could have a button like:
<button id="btnDownload" class="btn btn-info" asp-route-id="#HttpContext.Request.Query["id"]" asp-page-handler="PrintReceipt" type="submit">Submit</button>
Your element needs to have name=id for this to work, but you can change id to be anything.
Simply pass in string id as your parameter in your Controller method.
public async Task<IActionResult> PrintReceipt(string id){
//do something
}
You may need to add #Tran.Tran.Processing_ref_string in your if block
Below are some good links that could also steer you in the right direction. I hope this helps!
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/built-in/anchor-tag-helper?view=aspnetcore-3.1
TagHelper for passing route values as part of a link
https://www.learnrazorpages.com/razor-pages/tag-helpers/anchor-tag-helper

Changing HTML with Java(Spring)

I am a newbie at Spring and I am writing a rental system for movies. I have a Controller where I can get a list of all movies which are still rented (marked by digit "1" as a status in my Database) and which are already have been returned(marked as "0").
Now currently, when I call the page I see all the rented movies with the current status "1" or "0" as well as already returned movies which can still be returned and have a checkbox (which shouldn't be possible.
My question is, how can I change the HTML in the following way:
The status "1" or "0" changes to "rented" and "returned"
I want to remove the checkbox on all movies which already have been returned.
My code:
#Controller
public class MovieController {
#Autowired
private MovieService movieService;
#GetMapping("/home")
public String home(Model model) {
return "index";
}
#GetMapping("/movieList")
public String listAllMovies(Model model) {
model.addAttribute("listMovies", movieService.findNotRented());
return "movies";
}
#GetMapping("/search")
public String findByOption(Model model, #RequestParam(value = "search") String search,
#RequestParam("options") String options) {
if (options.equals("movie")) {
model.addAttribute("listMovies", movieService.findByName(search));
} else if (options.equals("actor")) {
model.addAttribute("listMovies", movieService.findMovieByActor(search));
} else if (options.equals("genre")) {
model.addAttribute("listMovies", movieService.findMovieByGenre(search));
}
return "movies";
}
#GetMapping("/rentedList")
public String findRentedMovies(Model model) {
model.addAttribute("listMovies", movieService.findRentedMovies());
return "rented";
}
#GetMapping("/rentMovie")
public String rentMovie(Model model, #RequestParam int id) {
model.addAttribute("listMovies", movieService.rentMovie(id));
return "index";
}
#GetMapping("/deleteRentedMovie")
public String deleterentedMovie(Model model, #RequestParam int id) {
model.addAttribute("listMovies", movieService.deleteRentedMovie(id));
return "index";
}
#GetMapping("/rentMovie2")
public String rentMovie2(Model model, #RequestParam("idChecked") List<Integer> id) {
if (id != null) {
for (Integer idInt : id) {
model.addAttribute("listMovies", movieService.rentMovie(idInt));
}
}
return "index";
}
#GetMapping("/deleteRentedMovie2")
public String deleterentedMovie(Model model, #RequestParam("idChecked") List<Integer> id) {
if (id != null) {
for (Integer idInt : id) {
model.addAttribute("listMovies", movieService.deleteRentedMovie(idInt));
}
}
return "index";
}
}
Thymeleaf
<h1>Movie List</h1>
<form action="/deleteRentedMovie2">
<table>
<tr>
<th>Title</th>
<th>Time rented</th>
<th>Status</th>
<th>Select</th>
</tr>
<tr th:each="movie : ${listMovies}">
<td th:text="${movie.title}"></td>
<td th:text="${movie.date}"></td>
<td th:text="${movie.status}"></td>
<td><input type="checkbox" th:name="idChecked" th:value="${movie.id}"></td>
</tr>
</table>
<input type="submit" value="Return Movie">
</form>
Thank you in advance and sorry for my bad English
For the status I would something like:
<td>
<span th:if="${movie.status == 1}">rented</span>
<span th:if="${movie.status == 0}">returned</span>
</td>
You could also use the Elvis operator but it is maybe less readable.
For the checkbox:
<td><input th:unless="${movie.status == 0}" type="checkbox" th:name="idChecked" th:value="${movie.id}"></td>

L2S aggregate binding pain

I have a fairly simple model:
public class Delivery
{
public int DeliveryId { get; set; }
public int OverId { get; set; }
public int Ball { get; set; }
public int Runs { get; set; }
public Player Player { get; set; }
}
All I want to do is have my collection of Delivery objects grouped by Player so that I can then perform some stats calculations on the results in my MVC3 view.
I'm almost there, but between the L2S query and my model binding declaration, I just can't get the two to marry up.
Doing it this way almost works:
var batting = from d in deliveries
where d.Over.IsBatting == true
group d by d.Player into player
select player;
return View(batting);
But the view bindings are a mess.
Bit of help?
EDIT:
Here's my view:
#model IEnumerable<IGrouping<Cricket.Models.Player, Cricket.Models.Delivery>>
#{
ViewBag.Title = "Batting";
}
<h2>Batting</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th></th>
<th>
OverId
</th>
<th>
Ball
</th>
<th>
Runs
</th>
</tr>
#foreach (var Item in Model) {
<tr>
<td>
#* #Html.ActionLink("Edit", "Edit", new { id=item.DeliveryId }) |
#Html.ActionLink("Details", "Details", new { id=item.DeliveryId }) |
#Html.ActionLink("Delete", "Delete", new { id=item.DeliveryId })
*# </td>
<td>
#Item.Key
</td>
<td>
#*item.Ball *#
</td>
<td>
#Item.Sum(x => x.Runs)
</td>
</tr>
}
</table>
You can create a ViewModel like
public class VMPlayerRuns
{
public Player _Player{get;set;}
public int Runs{get;set;}
}
and you can modify your query little bit like
var batting = from d in deliveries
where d.Over.IsBatting == true
group d by d.Player into player
from p in player
select new VMPlayerRuns{Player = p.Key, p.Sum(x=>x.Runs)};
Now you can bind this viewmodel to your view rather than complex clumsy grouping.
*Note:*There may be some syntax errors in query but this is whole idea of projecting grouping to your viewmodels