Skip to content

Commit

Permalink
Implementacao de Shaping Data
Browse files Browse the repository at this point in the history
  • Loading branch information
suarezrafael committed Aug 16, 2021
1 parent d092ca3 commit c59a193
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 5 deletions.
31 changes: 26 additions & 5 deletions CourseLibrary.API/Controllers/AuthorsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,27 @@ public class AuthorsController : ControllerBase
private readonly ICourseLibraryRepository _courseLibraryRepository;
private readonly IMapper _mapper;
private readonly IPropertyMappingService _propertyMappingService;
private readonly IPropertyCheckerService _propertyCheckerService;

public AuthorsController(ICourseLibraryRepository courseLibraryRepository,
IMapper mapper , IPropertyMappingService propertyMappingService)
IMapper mapper , IPropertyMappingService propertyMappingService,
IPropertyCheckerService propertyCheckerService
)
{
_courseLibraryRepository = courseLibraryRepository ??
throw new ArgumentNullException(nameof(courseLibraryRepository));
_mapper = mapper ??
throw new ArgumentNullException(nameof(mapper));
_propertyMappingService = propertyMappingService ??
throw new ArgumentNullException(nameof(propertyMappingService));

_propertyCheckerService = propertyCheckerService ??
throw new ArgumentNullException(nameof(propertyCheckerService));
}

[HttpGet(Name = "GetAuthors")]
[HttpHead]
public ActionResult<IEnumerable<AuthorDto>> GetAuthors(
public IActionResult GetAuthors(
[FromQuery] AuthorsResourceParameters authorsResourceParameters)
{
if (!_propertyMappingService.ValidMappingExistsFor<AuthorDto, Entities.Author>
Expand All @@ -42,6 +48,12 @@ public class AuthorsController : ControllerBase
return BadRequest();
}

if (!_propertyCheckerService.TypeHasProperties<AuthorDto>
(authorsResourceParameters.Fields))
{
return BadRequest();
}

var authorsFromRepo = _courseLibraryRepository.GetAuthors(authorsResourceParameters);

var previousPageLink = authorsFromRepo.HasPrevious ?
Expand All @@ -65,20 +77,26 @@ public class AuthorsController : ControllerBase
Response.Headers.Add("X-Pagination",
JsonSerializer.Serialize(paginationMetadata));

return Ok(_mapper.Map<IEnumerable<AuthorDto>>(authorsFromRepo));
return Ok(_mapper.Map<IEnumerable<AuthorDto>>(authorsFromRepo)
.ShapeData(authorsResourceParameters.Fields));
}

[HttpGet("{authorId}", Name = "GetAuthor")]
public IActionResult GetAuthor(Guid authorId)
public IActionResult GetAuthor(Guid authorId, string fields)
{
if (!_propertyCheckerService.TypeHasProperties<AuthorDto>
(fields))
{
return BadRequest();
}
var authorFromRepo = _courseLibraryRepository.GetAuthor(authorId);

if (authorFromRepo == null)
{
return NotFound();
}

return Ok(_mapper.Map<AuthorDto>(authorFromRepo));
return Ok(_mapper.Map<AuthorDto>(authorFromRepo).ShapeData(fields));
}

[HttpPost]
Expand Down Expand Up @@ -127,6 +145,7 @@ public ActionResult DeleteAuthor(Guid authorId)
return Url.Link("GetAuthors",
new
{
fields = authorsResourceParameters.Fields,
orderBy = authorsResourceParameters.OrderBy,
pageNumber = authorsResourceParameters.PageNumber - 1,
pageSize = authorsResourceParameters.PageSize,
Expand All @@ -137,6 +156,7 @@ public ActionResult DeleteAuthor(Guid authorId)
return Url.Link("GetAuthors",
new
{
fields = authorsResourceParameters.Fields,
orderBy = authorsResourceParameters.OrderBy,
pageNumber = authorsResourceParameters.PageNumber + 1,
pageSize = authorsResourceParameters.PageSize,
Expand All @@ -148,6 +168,7 @@ public ActionResult DeleteAuthor(Guid authorId)
return Url.Link("GetAuthors",
new
{
fields = authorsResourceParameters.Fields,
orderBy = authorsResourceParameters.OrderBy,
pageNumber = authorsResourceParameters.PageNumber,
pageSize = authorsResourceParameters.PageSize,
Expand Down
92 changes: 92 additions & 0 deletions CourseLibrary.API/Helpers/IEnumerableExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace CourseLibrary.API.Helpers
{
public static class IEnumerableExtensions
{
public static IEnumerable<ExpandoObject> ShapeData<TSource>( this IEnumerable<TSource> source, string fields)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
// create a list to hold our ExpandoObjects
var expandoObjectList = new List<ExpandoObject>();

// create a list with PropertyInfo objects on TSource. Reflection is
// expensive, so rather than doing if for each object in the list, we do
// it once and reuse the results. After all, part of the reflection is on the
// type of the object (TSource), not on the instance
var propertyInfoList = new List<PropertyInfo>();

if (string.IsNullOrWhiteSpace(fields))
{
// all public properties should be in the ExpandoObject
var propertyInfos = typeof(TSource)
.GetProperties(BindingFlags.Public | BindingFlags.Instance);

propertyInfoList.AddRange(propertyInfos);
}
else
{
// the field are separated by ",", so we split it.
var fieldsAfterSplit = fields.Split(',');

foreach (var field in fieldsAfterSplit)
{
// trim each field, as it might contain leading
// or trailing spaces. Can't trim the var in foreach,
// so use another var.
var propertyName = field.Trim();

// use reflection to get the property on the source object
// we need to include public and instance, b/c specifying a binding
// flag overwrites the already-existing binding flags.
var propertyInfo = typeof(TSource)
.GetProperty(propertyName, BindingFlags.IgnoreCase |
BindingFlags.Public | BindingFlags.Instance);

if (propertyInfo == null)
{
throw new Exception($"Property {propertyName} wasn't found on" +
$" {typeof(TSource)}");
}

// add propertyInfo to list
propertyInfoList.Add(propertyInfo);
}
}

// run through the source objects
foreach (TSource sourceObject in source)
{
// create an ExpandoObject that will hold the
// selected properties & values
var dataShapedObject = new ExpandoObject();

// Get the value of each property we have to return. For that,
// we run through the list
foreach (var propertyInfo in propertyInfoList)
{
// GetValue returns the value of the property on the source object
var propertyValue = propertyInfo.GetValue(sourceObject);

// add the field to the ExpandoObject
((IDictionary<string, object>)dataShapedObject)
.Add(propertyInfo.Name, propertyValue);
}

// add the ExpandoObject to the list
expandoObjectList.Add(dataShapedObject);
}

// return the list
return expandoObjectList;
}
}
}
77 changes: 77 additions & 0 deletions CourseLibrary.API/Helpers/ObjectExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace CourseLibrary.API.Helpers
{
public static class ObjectExtensions
{
public static ExpandoObject ShapeData<TSource>(this TSource source,
string fields)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}

var dataShapedObject = new ExpandoObject();

if (string.IsNullOrWhiteSpace(fields))
{
// all public properties should be in the ExpandoObject
var propertyInfos = typeof(TSource)
.GetProperties(BindingFlags.IgnoreCase |
BindingFlags.Public | BindingFlags.Instance);

foreach (var propertyInfo in propertyInfos)
{
// get the value of the property on the source object
var propertyValue = propertyInfo.GetValue(source);

// add the field to the ExpandoObject
((IDictionary<string, object>)dataShapedObject)
.Add(propertyInfo.Name, propertyValue);
}

return dataShapedObject;
}

// the field are separated by ",", so we split it.
var fieldsAfterSplit = fields.Split(',');

foreach (var field in fieldsAfterSplit)
{
// trim each field, as it might contain leading
// or trailing spaces. Can't trim the var in foreach,
// so use another var.
var propertyName = field.Trim();

// use reflection to get the property on the source object
// we need to include public and instance, b/c specifying a
// binding flag overwrites the already-existing binding flags.
var propertyInfo = typeof(TSource)
.GetProperty(propertyName,
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

if (propertyInfo == null)
{
throw new Exception($"Property {propertyName} wasn't found " +
$"on {typeof(TSource)}");
}

// get the value of the property on the source object
var propertyValue = propertyInfo.GetValue(source);

// add the field to the ExpandoObject
((IDictionary<string, object>)dataShapedObject)
.Add(propertyInfo.Name, propertyValue);
}

// return the list
return dataShapedObject;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ public class AuthorsResourceParameters
set => _pageSize = (value > maxPageSize) ? maxPageSize : value;
}
public string OrderBy { get; set; } = "Name";
public string Fields { get; set; }
}
}
7 changes: 7 additions & 0 deletions CourseLibrary.API/Services/IPropertyCheckerService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace CourseLibrary.API.Services
{
public interface IPropertyCheckerService
{
bool TypeHasProperties<T>(string fields);
}
}
47 changes: 47 additions & 0 deletions CourseLibrary.API/Services/PropertyCheckerService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace CourseLibrary.API.Services
{
public class PropertyCheckerService : IPropertyCheckerService
{
public bool TypeHasProperties<T>(string fields)
{
if (string.IsNullOrWhiteSpace(fields))
{
return true;
}

// the field are separated by ",", so we split it.
var fieldsAfterSplit = fields.Split(',');

// check if the requested fields exist on source
foreach (var field in fieldsAfterSplit)
{
// trim each field, as it might contain leading
// or trailing spaces. Can't trim the var in foreach,
// so use another var.
var propertyName = field.Trim();

// use reflection to check if the property can be
// found on T.
var propertyInfo = typeof(T)
.GetProperty(propertyName,
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

// it can't be found, return false
if (propertyInfo == null)
{
return false;
}
}

// all checks out, return true
return true;
}

}
}
1 change: 1 addition & 0 deletions CourseLibrary.API/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public void ConfigureServices(IServiceCollection services)
});

services.AddTransient<IPropertyMappingService,PropertyMappingService>();
services.AddTransient<IPropertyCheckerService, PropertyCheckerService>();
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

services.AddScoped<ICourseLibraryRepository, CourseLibraryRepository>();
Expand Down

0 comments on commit c59a193

Please sign in to comment.