ardalis / Result

A result abstraction that can be mapped to HTTP response codes if needed.
MIT License
866 stars 107 forks source link

TranslateResultToActionResult doesn't give PagedInfo #71

Open khandelwal-arpit opened 3 years ago

khandelwal-arpit commented 3 years ago

While using TranslateResultToActionResult to return responses in the form of ActionResults, there is no way for the caller to know the details of the pagination.

The reason is because the ToActionResult method in the ResultExtensions class just cares about the "result.GetValue()" and ignores the Pagination details which were fed by the upstream services as shown below:

return Result<IEnumerable<StoryDto>>.Success(pagedStoryDtos).ToPagedResult(pagedInfo);

Is there a way we can modify this behavior so that the caller can see the pagination details too?

ardalis commented 3 years ago

Good question; will need to review.

aurelienhayet commented 7 months ago

Hello Steve,

I am facing the same issue. Any news about the development of this feature ?

Aurélien

KyleMcMaster commented 7 months ago

This may not be the most elegant solution but given you have a StoryDto, could you create a PagedStoryDto (or whatever name) and use the Map method to combine StoryDto + PagedInfo into a single object that would get returned. Something like this:

return Result<IEnumerable<StoryDto>>.Success(pagedStoryDtos)
   .ToPagedResult(pagedInfo)
   .Map(r => new PagedStoryDto
   {
     StoryProperty = r.StoryProperty,
     PageNumber = r.PagedInfo.PageNumber,
     ... // other properties
   };

We might be able to add an overload of Map for paged results that does this out of the box in a future update. đŸ¤”

aurelienhayet commented 7 months ago

I did something a little bit more complex : I have overriden the TranslateResultToActionResultAttribute to deal with the paged results.

public class AdvancedTranslateResultToActionResultAttribute : TranslateResultToActionResultAttribute
{
    public override void OnActionExecuted(ActionExecutedContext context)
    {
        if ((context.Result as ObjectResult)?.Value is not IResult result) return;

        if (context.Controller is not ControllerBase controller) return;

        base.OnActionExecuted(context);

        if (result.Status is not ResultStatus.Ok) return;

        var type = result.GetType();
        var ardalisPagedResultType = typeof(PagedResult<>);
        if (type.Namespace != ardalisPagedResultType.Namespace
            || type.Name != ardalisPagedResultType.Name)
        {
            return;
        }

        if (!result.ValueType.IsAssignableTo(typeof(IEnumerable<object>))) return;

        var pagedInfoProperty = type.GetProperty(nameof(PagedInfo));

        if (pagedInfoProperty?.GetValue(result) is not PagedInfo pagedInfoValue) return;

        var pagedResultType = typeof(Contracts.Core.PagedResult<>);

        var itemType = result.ValueType.GetGenericArguments()[0];

        var constructed = pagedResultType.MakeGenericType(itemType);

        var pagedInfo = Contracts.Core.PagedInfo.FromArdalisPagedInfo(pagedInfoValue);

        context.Result = controller.StatusCode(
                    context.HttpContext.Response.StatusCode,
                    Activator.CreateInstance(constructed, pagedInfo, result.GetValue()));
    }
}

As you can see, I use reflection to Map Ardalis PagedResult and PagedInfo to my own equivalent classes Contracts.Core.Paged*

With this approach, I obtain a generic solution for all my controllers and paged models.