/// <summary> /// Returns a new collection of DTOs with only the fields specified by the IListParameters having values. /// </summary> /// <param name="mappedResult">The items to be trimmed.</param> /// <param name="parameters">The parameters by which to trim.</param> /// <returns>The trimmed collection of DTOs.</returns> public virtual IList <TDto> TrimListFields <TDto>(IList <TDto> mappedResult, IListParameters parameters) where TDto : IClassDto <T>, new() { if (parameters.Fields.Any()) { var allDtoProps = typeof(TDto).GetProperties(); var requestedProps = parameters.Fields .Select(field => allDtoProps.FirstOrDefault(p => string.Equals(p.Name, field, StringComparison.InvariantCultureIgnoreCase))) .Where(prop => prop != null) .ToList(); return(mappedResult .Select(dto => { var newDto = new TDto(); foreach (var prop in requestedProps) { prop.SetValue(newDto, prop.GetValue(dto)); } return newDto; }) .ToList()); } return(mappedResult); }
/// <summary> /// Applies any applicable sorting to the query. /// If the client has specified any sorting (found in ListParameters.OrderByList), /// this will delegate to ApplyListClientSpecifiedSorting. /// Otherwise, this will delegate to ApplyListDefaultSorting. /// This is called by GetListAsync when constructing a list result. /// </summary> /// <param name="query">The query to sort.</param> /// <param name="parameters">The parameters by which to filter and paginate.</param> /// <returns>The new query with additional sorting applied.</returns> public virtual IQueryable <T> ApplyListSorting(IQueryable <T> query, IListParameters parameters) { var orderByParams = parameters.OrderByList; if (orderByParams.Any()) { return(ApplyListClientSpecifiedSorting(query, parameters)); } else { return(ApplyListDefaultSorting(query)); } }
/// <summary> /// Get a mapped list of results using all the behaviors defined in the data source. /// </summary> /// <typeparam name="TDto">The IClassDto to map the data to.</typeparam> /// <returns>A ListResult containing the desired data mapped to the desired type.</returns> public virtual async Task <ListResult <TDto> > GetMappedListAsync <TDto>(IListParameters parameters) where TDto : IClassDto <T>, new() { var(result, tree) = await GetListAsync(parameters); if (!result.WasSuccessful || result.List == null) { return(new ListResult <TDto>(result)); } TransformResults(new ReadOnlyCollection <T>(result.List), parameters); var mappingContext = new MappingContext(Context.User, parameters.Includes); IList <TDto> mappedResult = result.List.Select(obj => Mapper.MapToDto <T, TDto>(obj, mappingContext, tree)).ToList(); mappedResult = TrimListFields(mappedResult, parameters); return(new ListResult <TDto>(result, mappedResult)); }
/// <summary> /// Applies paging to the query as specified by ListParameters.Page and PageSize. /// This is called by GetListAsync when constructing a list result. /// </summary> /// <param name="query">The query to filter.</param> /// <param name="parameters">The parameters by which to paginate.</param> /// <param name="totalCount">A known total count of results for the query, for limiting the maximum page.</param> /// <param name="page">out: The page number that was skipped to.</param> /// <param name="pageSize">out: The page size that was used in paging.</param> /// <returns></returns> public virtual IQueryable <T> ApplyListPaging(IQueryable <T> query, IListParameters parameters, int?totalCount, out int page, out int pageSize) { page = parameters.Page ?? 1; pageSize = parameters.PageSize ?? DefaultPageSize; pageSize = Math.Min(pageSize, MaxPageSize); pageSize = Math.Max(pageSize, 1); // Cap the page number at the last item if (totalCount.HasValue && (page - 1) * pageSize > totalCount) { page = (int)((totalCount - 1) / pageSize) + 1; } if (page > 1) { query = query.Skip((page - 1) * pageSize); } query = query.Take(pageSize); return(query); }
/// <summary> /// Get an unmapped list of results using all the behaviors defined in the DataSource. /// </summary> /// <returns>A ListResult with the requested data and paging information, /// and an IncludeTree to be used when mapping/serializing the data.</returns> public virtual async Task <(ListResult <T> List, IncludeTree IncludeTree)> GetListAsync(IListParameters parameters) { var query = GetQuery(parameters); query = ApplyListFiltering(query, parameters); // Get a count int totalCount = await GetListTotalCountAsync(query, parameters); // Add paging, sorting only after we've gotten the total count, since they don't affect counts. query = ApplyListSorting(query, parameters); query = ApplyListPaging(query, parameters, totalCount, out int page, out int pageSize); var canUseAsync = CanEvalQueryAsynchronously(query); List <T> result = canUseAsync ? await query.ToListAsync() : query.ToList(); var tree = GetIncludeTree(query, parameters); return(new ListResult <T>(result, page: page, totalCount: totalCount, pageSize: pageSize), tree); }
/// <summary> /// Applies user-specified sorting to the query. /// These sort parameters may be found in ListParameters.OrderByList. /// This is called by ApplyListSorting when constructing a list result. /// </summary> /// <param name="query">The query to sort.</param> /// <param name="parameters">The parameters by which to filter.</param> /// <returns>The new query with additional sorting applied.</returns> public virtual IQueryable <T> ApplyListClientSpecifiedSorting(IQueryable <T> query, IListParameters parameters) { var orderByParams = parameters.OrderByList; if (!orderByParams.Any(p => p.Key == "none")) { var clauses = orderByParams .Select(orderByParam => { string fieldName = orderByParam.Key; string direction = orderByParam.Value.ToString(); // Validate that the field accessor is a valid property // that the current user is allowed to read. var parts = fieldName.Split('.'); PropertyViewModel prop = null; foreach (var part in parts) { if (prop != null && !prop.IsPOCO) { // We're accessing a nested prop, but the parent isn't an object, // so this can't be valid. return(null); } prop = (prop?.Object ?? ClassViewModel).PropertyByName(part); // Check if the new prop exists and is readable by user. if (prop == null || !prop.IsClientProperty || !prop.SecurityInfo.IsReadable(User)) { return(null); } // If the prop is an object that isn't readable, then this is no good. if (prop.IsPOCO && !prop.Object.SecurityInfo.IsReadAllowed(User)) { return(null); } } if (prop.IsPOCO) { // The property is a POCO, not a value. // Get the default order by for the object's type to figure out what field to sort by. string clause = prop.Type.ClassViewModel.DefaultOrderByClause($"{fieldName}."); // The default order by clause has an order associated, but we want to override it // with the order that the client specified. A string replacement will do. return(clause .Replace("ASC", direction.ToUpper()) .Replace("DESC", direction.ToUpper())); } else { // We've validated that `fieldName` is a valid acccessor for a comparable property, // and that the user is allowed to read it. return($"{fieldName} {direction}"); } }) // Take all the clauses up until an invalid one is found. .TakeWhile(clause => clause != null) .ToList(); if (clauses.Any()) { query = query.OrderBy(string.Join(", ", clauses)); } } return(query); }
public void AppendListIdentifierParameter(StringBuilder query, IListParameters parameters) { AppendListIdentifierParameter(query, parameters.List); }