I want using chanel in backgroundservice, but I have this error when run my code, what I need to do.
Sorry for bad english
Unable to resolve service for type
'System.Threading.Channels.ChannelReader`1[SendMailChanel]'
while attempting to activate 'SendEmailService'
public class SendMailChanel
{
public List<SendMail> SendMails { get; set; }
public List<string> MailTos { get; set; }
}
public class SendEmailService: BackgroundService
{
private readonly ChannelReader<SendMailChanel> _channel;
public HostedService(
ChannelReader<SendMailChanel> channel)
{
_channel = channel;
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
await foreach (var item in _channel.ReadAllAsync(cancellationToken))
{
try
{
// do your work with data
}
catch (Exception e)
{
}
}
}
}
[ApiController]
[Route("api/data/upload")]
public class UploadController : ControllerBase
{
private readonly ChannelWriter<SendMailChanel> _channel;
public UploadController (
ChannelWriter<SendMailChanel> channel)
{
_channel = channel;
}
public async Task<IActionResult> Upload([FromForm] FileInfo fileInfo)
{
SendMailChanel mailChanel = new SendMailChanel();
mailChanel.SendMails = lstSendMail;
mailChanel.MailTos = lstEmailTo;
await _channel.WriteAsync(mailChanel);
return Ok();
}
}
Startup.cs
services.AddHostedService<SendEmailService>();
follow this guide
https://flerka.github.io/personal-blog/2020-01-23-communication-with-hosted-service-using-channels/
services.AddHostedService<SendEmailService>();
services.AddSingleton<Channel<SendMailChanel>>(Channel.CreateUnbounded<SendMailChanel>(new UnboundedChannelOptions() { SingleReader = true }));
services.AddSingleton<ChannelReader<SendMailChanel>>(svc => svc.GetRequiredService<Channel<SendMailChanel>>().Reader);
services.AddSingleton<ChannelWriter<SendMailChanel>>(svc => svc.GetRequiredService<Channel<SendMailChanel>>().Writer);
Related
I have a Category table in my project, I used Generic repository and Unit of Work pattern in my project. It is an API. It is working when I use mock of UnitOfWork lonely but It is not working when I want to check my controller result. My controller response is 404 always. I think it is because of my mistake in setup but I don't know where it is.
`
[DisplayName("Category Table")]
public class Category
{
public int Id { get; set; }
public int? Priority { get; set; }
public string Name { get; set; }
public string? Details { get; set; }
public bool Enabled { get; set; }
public int? ParentCategoryId { get; set; }
public virtual Category Parent { get; set; }
public virtual IList<Category> Children { get; set; }
}
public interface IGenericRepository<T> where T : class, new()
{
Task<T> GetByIdAsync(object id);
Task<IReadOnlyList<T>> ListAllAsync();
Task<T> GetEntityWithSpec(ISpecification<T> spec);
Task<IReadOnlyList<T>> ListWithSpecAsync(ISpecification<T> spec);
...
}
public class GenericRepository<T> : IGenericRepository<T> where T : class, new()
{
private readonly FactoryContext _context;
public GenericRepository(FactoryContext context)
{
_context = context;
}
public async Task<T> GetByIdAsync(object id)
{
return await _context.Set<T>().FindAsync(id);
}
public async Task<IReadOnlyList<T>> ListAllAsync()
{
return await _context.Set<T>().ToListAsync();
}
public async Task<T> GetEntityWithSpec(ISpecification<T> spec)
{
return await ApplySpecification(spec).FirstOrDefaultAsync();
}
public async Task<IReadOnlyList<T>> ListWithSpecAsync(ISpecification<T> spec)
{
return await ApplySpecification(spec).ToListAsync();
}
...
private IQueryable<T> ApplySpecification(ISpecification<T> spec)
{
return SpecificationEvaluator<T>.GetQuery(_context.Set<T>().AsQueryable(), spec);
}
}
public interface IUnitOfWork : IDisposable
{
IGenericRepository<TEntity> Repository<TEntity>() where TEntity : class, new();
Task<int> Complete();
}
public class UnitOfWork : IUnitOfWork
{
private readonly FactoryContext _context;
private Hashtable _repositories;
public UnitOfWork(FactoryContext context)
{
_context = context;
}
public async Task<int> Complete()
{
return await _context.SaveChangesAsync();
}
public void Dispose()
{
_context.Dispose();
}
public IGenericRepository<TEntity> Repository<TEntity>() where TEntity : class, new()
{
if (_repositories == null) _repositories = new Hashtable();
var type = typeof(TEntity).Name;
if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(GenericRepository<>);
var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(TEntity)), _context);
_repositories.Add(type, repositoryInstance);
}
return (IGenericRepository<TEntity>)_repositories[type];
}
}
public class CategoriesController : BaseApiController
{
private readonly IUnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public CategoriesController(IUnitOfWork unitOfWork, IMapper mapper)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
}
[HttpGet("getcategorybyid/{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status400BadRequest)]
public async Task<ActionResult<CategoryToReturnDto>> GetCategoryById(int id)
{
try
{
var spec = new GetCategoriesWithParentsSpecification(id);
var category = await _unitOfWork.Repository<Category>().GetEntityWithSpec(spec);
if (category.Id == 0) return NotFound(new ApiResponse(404));
var returnCategories = _mapper.Map<Category, CategoryToReturnDto>(category);
return new OkObjectResult(new ApiResponse(200, "Ok", returnCategories));
}
catch (Exception ex)
{
return BadRequest(new ApiResponse(400, ex.Message));
}
}
}
public class CategoriesControllerTests
{
private readonly Mock<IMapper> _mapper;
private readonly Mock<IUnitOfWork> _unitOfWork;
public CategoriesControllerTests()
{
_mapper = new Mock<IMapper>();
_unitOfWork = new Mock<IUnitOfWork>();
}
[Fact]
public async Task Get_OK_ObjectResult_CategoryById()
{
//Arrange
Category newCategory = CreateTestCategory();
var spec = new GetCategoriesWithParentsSpecification(1);
_unitOfWork.Setup(x => x.Repository<Category>().GetEntityWithSpec(spec)).ReturnsAsync(newCategory)
.Verifiable();
//Act
// Here it is working and result2 has data.
var result2 = await _unitOfWork.Object.Repository<Category>().GetEntityWithSpec(spec);
var controller = new CategoriesController(_unitOfWork.Object, _mapper.Object);
var result = await controller.GetCategoryById(1);
// Assert
result.Value?.Id.ShouldBe(newCategory.Id);
result.Value?.Name.ShouldBeEquivalentTo(newCategory.Name);
result.Value?.Name.ShouldBeEquivalentTo("newCategory.Name");
// My problem here. My result is NotFoundObjectResult always.
result.Result.ShouldBeOfType<OkObjectResult>();
}
private Category CreateTestCategory()
{
return new Category()
{
Id = 1,
Priority = 1,
Name = "Test Category",
Enabled = true,
Details = "Testing category data"
};
}
}
`
The spec that you pass during the moq setup isn't the same as the spec that your repository receives inside the controller
In your test, you should change the setup in such a way that it checks the type of input instead and avoid passing a reference.
_unitOfWork.Setup(x => x.Repository<Category>()
.GetEntityWithSpec(It.IsAny<ISpecification<Category>>()))
.ReturnsAsync(newCategory)
.Verifiable();
Here we set up the moq in a way that as long as the input is ISpecification<Category>, it returns newCategory
The specificatio didn't use truly. Your setup should be like this:
_unitOfWork.Setup(x => x.Repository<Category>()
.GetEntityWithSpec(It.IsAny<ISpecification<Category>>()))
.ReturnsAsync(newCategory)
.Verifiable();
In my ASP.NER Core Web API Project, I have this code:
public interface IUriPaginationService
{
public Uri GetPaginationUri(int page, string actionUrl);
}
public class UriPaginationService : IUriPaginationService
{
private readonly string _baseUri;
public UriPaginationService(string baseUri)
{
_baseUri = baseUri;
}
public Uri GetPaginationUri(int page, string actionUrl)
{
string baseUrl = $"{_baseUri}{actionUrl}";
return new Uri(baseUrl);
}
}
public abstract class AuditableBaseEntity
{
public AuditableBaseEntity()
{
CreatedDate = DateTime.Now;
}
[Key]
public virtual long Id { get; set; }
[JsonIgnore]
public bool IsDeleted { get; set; }
[DataType(DataType.DateTime)]
public DateTime? CreatedDate { get; set; }
public long? LastUpdatedBy { get; set; }
}
Repositories:
public interface IBaseRepository<T> where T : AuditableBaseEntity
{
Task<IEnumerable<T>> GetAll();
Task<T> GetById(long id);
bool EntityExists(long id);
}
public class BaseRepository<T> : IBaseRepository<T> where T : AuditableBaseEntity
{
private readonly DDMDbContext _context;
private DbSet<T> _entities;
public BaseRepository(DDMDbContext context)
{
_context = context;
_entities = context.Set<T>();
}
public async Task<IEnumerable<T>> GetAll()
{
var list = await _entities.Where(x => x.IsDeleted == false).ToListAsync();
return list;
}
public async Task<T> GetById(long id)
{
return await _entities.FindAsync(id);
}
public bool EntityExists(long id)
{
return _entities.Any(x => x.Id == id && x.IsDeleted == false);
}
}
UnitOfWork:
public interface IUnitOfWork : IDisposable
{
IBaseRepository<Merchant> MerchantRepository { get; }
}
public class UnitOfWork : IUnitOfWork
{
private readonly DDMDbContext _context;
public UnitOfWork(DDMDbContext context)
{
_context = context;
}
private readonly IBaseRepository<Merchant> _merchantRepository;
public IBaseRepository<Merchant> MerchantRepository => _merchantRepository ?? new BaseRepository<Merchant>(_context);
public void Dispose()
{
if (_context != null)
{
_context.Dispose();
}
}
}
Services:
public interface IMerchantService
{
public Task<IEnumerable<Merchant>> GetMerchants();
public bool EntityExists(long id);
public Task<ResponsePagination<GenericPagination<MerchantGetDto>>> GetAll(int page, int sizeByPage);
}
public class MerchantService : IMerchantService
{
private readonly IUnitOfWork _unitOfWork;
private readonly IUriPaginationService _uriPaginationService;
private readonly DDMDbContext _context;
public MerchantService(IUnitOfWork unitOfWork, IUriPaginationService uriPaginationService, DDMDbContext context)
{
_unitOfWork = unitOfWork;
_uriPaginationService = uriPaginationService;
_context = context;
}
public async Task<IEnumerable<Merchant>> GetMerchants()
{
return await _unitOfWork.MerchantRepository.GetAll();
}
public async Task<ResponsePagination<GenericPagination<MerchantGetDto>>> GetAll(int page, int sizeByPage)
{
string nextRoute = null, previousRoute = null;
IEnumerable<Merchant> data = await _unitOfWork.MerchantRepository.GetAll();
var mapper = new EntityMapper();
var merchantsDto = data.Select(m => mapper.FromMerchantToMerchantGetDto(m)).ToList();
GenericPagination<MerchantGetDto> objGenericPagination = GenericPagination<MerchantGetDto>.Create(merchantsDto, page, sizeByPage);
ResponsePagination<GenericPagination<MerchantGetDto>> response = new ResponsePagination<GenericPagination<MerchantGetDto>>(objGenericPagination);
response.CurrentPage = objGenericPagination.CurrentPage;
response.HasNextPage = objGenericPagination.HasNextPage;
response.HasPreviousPage = objGenericPagination.HasPreviousPage;
response.PageSize = objGenericPagination.PageSize;
response.TotalPages = objGenericPagination.TotalPages;
response.TotalRecords = objGenericPagination.TotalRecords;
response.Data = objGenericPagination;
if (response.HasNextPage)
{
nextRoute = $"/merchants?page={(page + 1)}";
response.NextPageUrl = _uriPaginationService.GetPaginationUri(page, nextRoute).ToString();
}
else
{
response.NextPageUrl = null;
}
if (response.HasPreviousPage)
{
previousRoute = $"/merchants?page={(page - 1)}";
response.PreviousPageUrl = _uriPaginationService.GetPaginationUri(page, previousRoute).ToString();
}
else
{
response.PreviousPageUrl = null;
}
return response;
}
public bool EntityExists(long id)
{
return _unitOfWork.MerchantRepository.EntityExists(id);
}
public async Task<Merchant> GetById(long id)
{
return await _unitOfWork.MerchantRepository.GetById(id);
}
}
Controller:
[Produces("application/json")]
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class AdminController : ControllerBase
{
private readonly IMerchantService _merchantService;
public AdminController(MerchantService merchantService)
{
_merchantService = merchantService;
}
[HttpGet("merchants")]
public async Task<ResponsePagination<GenericPagination<MerchantGetDto>>> GetAll(int page, int sizeByPage)
{
return await _merchantService.GetAll(page, sizeByPage);
}
}
startup:
public void ConfigureServices(IServiceCollection services)
{
// services.AddMvc();
services.AddControllers();
services.AddDb(Configuration);
services.AddJwtAuthentication(Configuration);
services.AddMvcCoreFramework(Configuration);
services.AddAppAuthorization(Configuration);
services.AddControllersWithViews();
services.AddRazorPages();
services.AddVersioning();
services.AddSwagger();
services.AddRouting(options => options.LowercaseUrls = true);
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IUriPaginationService>(provider =>
{
var accesor = provider.GetRequiredService<IHttpContextAccessor>();
var request = accesor.HttpContext.Request;
var absoluteUri = string.Concat(request.Scheme, "://", request.Host.ToUriComponent());
return new UriPaginationService(absoluteUri);
});
services.AddScoped(typeof(IBaseRepository<>), typeof(BaseRepository<>));
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddTransient<IMerchantService, MerchantService>();
}
When I use this on postman get request:
https://localhost:44341/api/v1/merchant/mandates
I got this error:
System.InvalidOperationException: Unable to resolve service for type 'API.Infrastructure.Services.Concrete.MerchantService' while attempting to activate 'API.Web.Controllers.v1.MerchantController'.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
How do I resolve this:
Thanks
The controller is dependent on the implementation (class)
public AdminController(MerchantService merchantService)
{
_merchantService = merchantService;
}
while you registered the abstraction (interface).
services.AddTransient<IMerchantService, MerchantService>();
Based on the registration it is most likely the controller was meant to be dependent on the interface (abstraction)
public AdminController(IMerchantService merchantService) //<-- CHANGED TO INTERFACE
{
_merchantService = merchantService;
}
Updating the controller constructor to be dependent on the registered abstraction will fix the shown exception.
in addition to Nkosi answer, the three injection service are listed below and their scope. three methods define the lifetime of the services
AddTransient
Transient lifetime services are created each time they are requested. This lifetime works best for lightweight, stateless services.
AddScoped
Scoped lifetime services are created once per request.
AddSingleton
Singleton lifetime services are created the first time they are requested (or when ConfigureServices is run if you specify an instance there) and then every subsequent request will use the same instance.
Brief explation can be found here
I need to make an API in ASP.NET 5 with a "Server" entity that has four attributes:
public class Server
{
public Guid ServerId { get; private set; }
public string Name { get; set; }
public string Ip { get; private set; }
public int Port { get; private set; }
}
I'm having difficulties in returning the IP and the Port, because I don't know where and how exactly I should return these values. I thought maybe it could be on "ServersController", but it's also not working with the previous answers from StackOverflow.
Here's a chunk of ServersController's code:
[Route("api/servers")]
[ApiController]
public class ServersController : ControllerBase
{
private readonly ServerContext _context;
public ServersController(ServerContext context)
{
_context = context;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<Server>>> GetServers()
{
return await _context.Servers.ToListAsync();
}
[HttpGet("{id}")]
public async Task<ActionResult<Server>> GetServer(Guid id)
{
var server = await _context.Servers.FindAsync(id);
if (server == null)
{
return NotFound();
}
return server;
}
[HttpPost]
public async Task<ActionResult<Server>> PostServer(Server server)
{
_context.Servers.Add(server);
await _context.SaveChangesAsync();
return CreatedAtAction("GetServer", new { id = server.ServerId }, server);
}
I'm struggling to find a way to catch an exception thrown by a model property (actually by its type struct), which must be bound to a POST request body data.
I have a general scenario where I need to treat very specific data types, so I'm using structs to validate them accordingly each case.
Despite of the following codes are just drafts, all suggestions are very welcome!
So the following is an example of a Controller:
[ApiController]
[TypeFilter(typeof(CustomExceptionFilter))]
public class OrdersController : ControllerBase
{
public OrdersController(ILogger<OrdersController> logger, IDataAccess dataAccess)
{
_dataAccess = dataAccess;
_logger = logger;
}
private readonly IDataAccess _dataAccess;
private readonly ILogger<OrdersController> _logger;
[EnableCors]
[Route("api/[controller]/Sales")]
[HttpPost]
public async Task<ActionResult> PostSaleAsync(
[FromBody] SaleOrder saleOrder)
{
try
{
Guid saleOrderId = Guid.NewGuid();
saleOrder.SaleOrderId = saleOrderId;
foreach (SaleOrderItem item in saleOrder.items)
item.SaleOrderId = saleOrderId;
OrderQuery query = new OrderQuery(_dataAccess);
await query.SaveAsync(saleOrder);
_dataAccess.Commit();
var response = new
{
Error = false,
Message = "OK",
Data = new
{
SaleOrderId = saleOrderId
}
};
return Ok(response);
}
catch (DataAccessException)
{
_dataAccess.Rollback();
//[...]
}
//[...]
}
}
and an example of a model, Order, and a struct, StockItemSerialNumber:
public class SaleOrder : Order
{
public Guid SaleOrderId { get => OrderId; set => OrderId = value; }
public Guid CustomerId { get => StakeholderId; set => StakeholderId = value; }
public Guid? SellerId { get; set; }
public SaleModelType SaleModelType { get; set; }
public SaleOrderItem[] items { get; set; }
}
public class SaleOrderItem : OrderItem
{
public Guid SaleOrderId { get; set; }
public StockItemSerialNumber StockItemSerialNumber { get; set; }
//[JsonConverter(typeof(StockItemSerialNumberJsonConverter))]
//public StockItemSerialNumber? StockItemSerialNumber { get; set; }
}
public struct StockItemSerialNumber
{
public StockItemSerialNumber(string value)
{
try
{
if ((value.Length != 68) || Regex.IsMatch(value, #"[^\w]"))
throw new ArgumentOutOfRangeException("StockItemSerialNumber");
_value = value;
}
catch(RegexMatchTimeoutException)
{
throw new ArgumentOutOfRangeException("StockItemSerialNumber");
}
}
private string _value;
public static implicit operator string(StockItemSerialNumber value) => value._value;
public override string ToString() => _value;
}
I would like to catch ArgumentOutOfRangeException thrown by StockItemSerialNumber struct and then return a response message informing a custom error accordingly.
Since this exception is not catch by the try...catch block from Controller, I've tried to build a class that extends IExceptionFilter and add as a filter:
public class CustomExceptionFilter : IExceptionFilter
{
private readonly IWebHostEnvironment _hostingEnvironment;
private readonly IModelMetadataProvider _modelMetadataProvider;
public CustomExceptionFilter(
IWebHostEnvironment hostingEnvironment,
IModelMetadataProvider modelMetadataProvider)
{
_hostingEnvironment = hostingEnvironment;
_modelMetadataProvider = modelMetadataProvider;
}
public void OnException(ExceptionContext context)
{
context.Result = new BadRequestObjectResult(new {
Error = false,
Message = $"OPS! Something bad happened, Harry :( [{context.Exception}]."
});
}
}
Startup.cs :
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
// [...]
});
services.AddTransient<IDataAccess>(_ => new DataAccess(Config.DBCredentials));
services.AddControllers(options => options.Filters.Add(typeof(CustomExceptionFilter)));
}
But that approach also doesn't work, I mean, the ArgumentOutOfRangeException remains not being catch and crashes the execution during a POST request.
Finally, here's an example of a JSON with request data:
{
"CustomerId":"fb2b0555-6d32-404b-b2f0-50032a7e0f59",
"SellerId":null,
"items": [
{
"StockItemSerialNumber":"22B6E75510AB459B8DB2874F20C722B6F3DC19C6E474337D5F73BB87699E9A1001"
}, // Invalid
{
"StockItemSerialNumber":"022B6E755122B659B8DB2874F20C780030F3DC19C6E47465AS1673BB87699E9A1001"
} // Valid
]
}
So I appreciate any help or suggestion! Thanks!
I have a service that calls endpoints from a external API. I developed a custom middleware for handling possible exceptions from these API calls and logging them. However, although the middleware includes a try and catch block in the Invoke method, my application's flow is being interrupted after an Exception is caught by the catch block. The flow simply ends, although the application keeps running. As this application is executing jobs that sync data, I need that the flow continues (and possible exceptions be logged, as I mentioned).
Anyone has already experienced this or have any clue how to make the flow go on? Is there a better approach for doing this? I'm totally open for suggestions, please.
This is my code:
public class ApiException
{
private readonly RequestDelegate _next;
private readonly ILogger<ApiException> _logger;
private readonly ApiExceptionOptions _options;
public ApiException(ApiExceptionOptions options, RequestDelegate next,
ILogger<ApiException> logger)
{
_next = next;
_logger = logger;
_options = options;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private Task HandleExceptionAsync(HttpContext context, Exception exception)
{
ApiError error = _options.AddResponseDetails?.Invoke(exception)
?? ApiErrorFactory.New(exception);
LogApiException(exception, error);
return CreateResponse(context, error);
}
private static Task CreateResponse(HttpContext context, ApiError error)
{
var result = JsonSerializer.Serialize(error,
new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)error.Status;
return context.Response.WriteAsync(result);
}
private void LogApiException(Exception exception, ApiError error)
{
var innerExMessage = GetInnermostExceptionMessage(exception);
var level = _options.DetermineLogLevel?.Invoke(exception) ?? GetLogLevel(exception);
_logger.Log(level, exception, "ERROR: {message} -- {ErrorId}.", innerExMessage, error.Id);
}
private static LogLevel GetLogLevel(Exception exception) =>
exception switch
{
InvalidInputException _ => LogLevel.Warning,
_ => LogLevel.Error
};
private string GetInnermostExceptionMessage(Exception exception)
{
if (exception.InnerException != null)
return GetInnermostExceptionMessage(exception.InnerException);
return exception.Message;
}
}
public class ApiExceptionOptions
{
public Func<Exception, ApiError> AddResponseDetails { get; set; }
public Func<Exception, LogLevel> DetermineLogLevel { get; set; }
}
public static class ApiErrorFactory
{
public static ApiError New(Exception exception) =>
exception switch
{
InvalidInputException e => new ApiError
{
Details = e.Details,
Message = e.Message,
Status = BadRequest
},
_ => new ApiError()
};
}
public class ApiError
{
public static string DefaultErrorMessage = "Erro no processamento da requisição.";
public string Id { get; } = Guid.NewGuid().ToString();
public HttpStatusCode Status { get; set; } = HttpStatusCode.InternalServerError;
public string Title { get; set; } = "API Error";
public string Message { get; set; } = DefaultErrorMessage;
public IDictionary<string, object> Details { get; set; } = new Dictionary<string, object>();
}
public static class ApiExceptionExtensions
{
public static IApplicationBuilder UseApiExceptionHandler(this IApplicationBuilder builder)
{
var options = new ApiExceptionOptions();
return builder.UseMiddleware<ApiException>(options);
}
public static IApplicationBuilder UseApiExceptionHandler(this IApplicationBuilder builder,
Action<ApiExceptionOptions> configureOptions)
{
var options = new ApiExceptionOptions();
configureOptions(options);
return builder.UseMiddleware<ApiException>(options);
}
}