Last active
November 15, 2017 05:22
-
-
Save frankradocaj/683ef42ab402bb51ceba9ac3c859e8c3 to your computer and use it in GitHub Desktop.
Simplifying ASP.Net Controllers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using Microsoft.AspNetCore.Mvc; | |
using System.Net; | |
using Newtonsoft.Json.Serialization; | |
using System.Text; | |
namespace WebApplication1 | |
{ | |
public interface IServiceResponse | |
{ | |
object Response { get; } | |
} | |
public interface IServiceSuccess : IServiceResponse { } | |
public interface IServiceFailure : IServiceResponse | |
{ | |
ServiceFailureResponse.ErrorResponse Error { get; } | |
} | |
public class ServiceResponse | |
{ | |
public static ServiceSuccessResponse Success() | |
{ | |
return new ServiceSuccessResponse | |
{ | |
Response = new ServiceSuccessResponse.NoResponse() | |
}; | |
} | |
public static ServiceSuccessResponse Success<T>(T response) | |
{ | |
return new ServiceSuccessResponse | |
{ | |
Response = response | |
}; | |
} | |
public static ServiceFailureResponse Failure(string errorCode) | |
{ | |
return new ServiceFailureResponse | |
{ | |
Error = new ServiceFailureResponse.ErrorResponse | |
{ | |
ErrorCode = errorCode | |
} | |
}; | |
} | |
} | |
public class ServiceSuccessResponse : IServiceSuccess | |
{ | |
public object Response { get; set; } | |
public class NoResponse { } | |
} | |
public class ServiceFailureResponse : IServiceFailure | |
{ | |
object IServiceResponse.Response => Error; | |
public ErrorResponse Error { get; set; } | |
public class ErrorResponse | |
{ | |
public string ErrorCode { get; set; } | |
} | |
} | |
public interface IPagedList | |
{ | |
int Page { get; } | |
int PageSize { get; } | |
int TotalResultCount { get; } | |
IEnumerable<object> Items { get; } | |
} | |
public interface IPagedList<T> : IPagedList | |
{ | |
new IEnumerable<T> Items { get; } | |
} | |
public sealed class PagedList<T> : IPagedList<T> | |
{ | |
public IEnumerable<T> Items { get; set; } | |
IEnumerable<object> IPagedList.Items => Items.OfType<object>(); | |
public int Page { get; } | |
public int PageSize { get; } | |
public int TotalResultCount { get; } | |
} | |
public abstract class FrankController : Controller | |
{ | |
public abstract HttpStatusCode GetErrorHttpStatusCode(string errorCode); | |
public JsonResult ApiResult(IServiceResponse response, string pagingPath, HttpStatusCode? successResponseHttpStatusCodeOverride = null) | |
{ | |
JsonResult jsonResult; | |
if (response.Response is IPagedList pagedList) | |
{ | |
if (string.IsNullOrWhiteSpace(pagingPath)) | |
{ | |
throw new ApplicationException("Must specify a pagePath for a PagedList response"); | |
} | |
SetPagingHeaders(pagedList, pagingPath); | |
jsonResult = new JsonResult(pagedList.Items); | |
} | |
else | |
{ | |
jsonResult = new JsonResult(response.Response); | |
} | |
HttpStatusCode httpStatusCode; | |
if (response is IServiceFailure failureResponse) | |
{ | |
httpStatusCode = GetErrorHttpStatusCode(failureResponse.Error.ErrorCode); | |
} | |
else | |
{ | |
httpStatusCode = successResponseHttpStatusCodeOverride ?? HttpStatusCode.OK; // Default | |
} | |
jsonResult.StatusCode = (int)httpStatusCode; | |
jsonResult.SerializerSettings = new Newtonsoft.Json.JsonSerializerSettings | |
{ | |
Formatting = Newtonsoft.Json.Formatting.Indented, | |
ContractResolver = new CamelCasePropertyNamesContractResolver() | |
}; | |
return jsonResult; | |
} | |
public JsonResult ApiResult(IServiceResponse response, HttpStatusCode? successResponseHttpStatusCodeOverride = null) | |
{ | |
return ApiResult(response, null, successResponseHttpStatusCodeOverride); | |
} | |
/// <summary> | |
/// Content-Range is structured as such: "0-9/76" or */0 for no results. | |
/// | |
/// If there are additional results - ie: another page, the "Link" | |
void SetPagingHeaders(IPagedList pagedList, string path) | |
{ | |
if (pagedList.TotalResultCount == 0) | |
{ | |
Response.Headers["Content-Range"] = "*/0"; | |
} | |
else | |
{ | |
if (pagedList.Page * pagedList.PageSize >= pagedList.TotalResultCount) | |
{ | |
Response.Headers["Content-Range"] = ContentRangeGenerator(pagedList); | |
} | |
else | |
{ | |
Response.Headers["Content-Range"] = ContentRangeGenerator(pagedList); | |
Response.Headers["Link"] = LinkGenerator(pagedList, path); | |
} | |
} | |
} | |
string LinkGenerator(IPagedList pagedList, string path) | |
{ | |
const string ApiRootUri = ""; | |
var strFormat = @"<{0}{1}?page={2}&count={3}>; rel=""{4}"""; | |
var str = new StringBuilder(); | |
// First page | |
str.Append(string.Format(strFormat, ApiRootUri, path, 1, pagedList.PageSize, "first")); | |
// Previous page | |
if (pagedList.Page > 1) | |
{ | |
str.Append(","); | |
str.Append(string.Format(strFormat, ApiRootUri, path, pagedList.Page * pagedList.PageSize, pagedList.PageSize, "prev")); | |
} | |
// Next page | |
if (pagedList.Page * pagedList.PageSize < pagedList.TotalResultCount) | |
{ | |
str.Append(","); | |
str.Append(string.Format(strFormat, ApiRootUri, path, pagedList.Page + pagedList.PageSize, pagedList.PageSize, "next")); | |
} | |
// Last page | |
int lastPage = (pagedList.TotalResultCount % pagedList.PageSize) == 0 ? pagedList.TotalResultCount - pagedList.PageSize : pagedList.TotalResultCount - (pagedList.TotalResultCount % pagedList.PageSize); | |
str.Append(","); | |
str.Append(string.Format(strFormat, ApiRootUri, path, lastPage, pagedList.PageSize, "last")); | |
return str.ToString(); | |
} | |
string ContentRangeGenerator(IPagedList pagedList) | |
{ | |
var pageEnd = pagedList.Page * pagedList.PageSize; | |
var pageStart = pageEnd - pagedList.PageSize + 1; | |
return string.Format("{0}-{1}/{2}", pageStart, pageEnd < pagedList.TotalResultCount ? pageEnd : pagedList.TotalResultCount, pagedList.TotalResultCount); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.AspNetCore.Mvc; | |
using System.Net; | |
namespace WebApplication1 | |
{ | |
[Route("api/values")] | |
public class ValuesController : FrankController | |
{ | |
readonly IValuesService _valuesService; | |
public ValuesController(IValuesService valuesService) | |
{ | |
_valuesService = valuesService; | |
} | |
public override HttpStatusCode GetErrorHttpStatusCode(string errorCode) | |
{ | |
switch (errorCode) | |
{ | |
case ValuesServiceErrorCodes.INVALID_CRITERIA: return HttpStatusCode.BadRequest; | |
default: return HttpStatusCode.BadRequest; | |
} | |
} | |
// GET api/values | |
[HttpGet] | |
public JsonResult Get(ValuesQuery request) => ApiResult(_valuesService.SearchValues(request)); | |
// GET api/values/{id:guid} | |
[HttpGet("{id}")] | |
public JsonResult Get(ValueRequest request) => ApiResult(_valuesService.GetValue(request)); | |
// POST api/values | |
[HttpPost] | |
public void Post([FromBody][FromRoute]AddValueRequest request) => ApiResult(_valuesService.AddValue(request)); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace WebApplication1.Controllers | |
{ | |
public interface IValuesService | |
{ | |
IServiceResponse SearchValues(ValuesQuery request); | |
IServiceResponse GetValue(ValueRequest request); | |
IServiceResponse AddValue(AddValueRequest request); | |
} | |
public static class ValuesServiceErrorCodes | |
{ | |
public const string INVALID_CRITERIA = "invalid_criteria"; | |
} | |
public class ValuesService : IValuesService | |
{ | |
public IServiceResponse SearchValues(ValuesQuery request) | |
{ | |
return ServiceResponse.Success(new PagedList<object> | |
{ | |
Items = new object[] { } | |
}); | |
} | |
public IServiceResponse GetValue(ValueRequest request) | |
{ | |
if (request.Id == new Guid("e551a7b7-5681-4b9b-b470-dca7062731f0")) | |
{ | |
return ServiceResponse.Success(new | |
{ | |
Id = new Guid("e551a7b7-5681-4b9b-b470-dca7062731f0"), | |
Name = "Frank" | |
}); | |
} | |
return ServiceResponse.Failure(ValuesServiceErrorCodes.INVALID_CRITERIA); | |
} | |
public IServiceResponse AddValue(AddValueRequest request) | |
{ | |
return ServiceResponse.Success(new | |
{ | |
Id = new Guid("e551a7b7-5681-4b9b-b470-dca7062731f0") | |
}); | |
} | |
} | |
public class ValuesQuery { } | |
public class ValueRequest | |
{ | |
public Guid Id { get; set; } | |
public string Name { get; set; } | |
} | |
public class AddValueRequest | |
{ | |
public string Name { get; set; } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment