Headers are read-only, response has already started - asp.net

I am trying to catch and format the exception thrown by the resource filter but getting this error. The middleware is working for exceptions thrown from controller level but getting this - "System.InvalidOperationException: Headers are read-only, response has already started" error while trying to write to the response in case of resource level errors.
Code of my Resource Filter:
public class TestingAsyncResourceFilter : IAsyncResourceFilter
{
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
Console.WriteLine("Resource filter executing");
var resourceExecutedContext = await next();
Console.WriteLine("Resource filter executed");
if (!resourceExecutedContext.ModelState.IsValid)
{
throw new CustomUPException();
}
}
}
Code of middleware:
public class ResponseFormatterMiddleware : IMiddleware
{
private readonly ILogger<ResponseFormatterMiddleware> _logger;
public ResponseFormatterMiddleware(ILogger<ResponseFormatterMiddleware> logger)
{
_logger = logger;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
Console.WriteLine("Before execution");
await next(context);
Console.WriteLine("After Execution");
}
catch(CustomUPException e)
{
Console.WriteLine("Here we are");
await context.Response.WriteAsJsonAsync(
new ResponseDto()
{
statusCode = e.statusCode,
message = e.message
}); // getting error
}
catch(Exception e)
{
_logger.LogError(e.Message);
context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
await context.Response.WriteAsJsonAsync(
new ResponseDto()
{
success = false,
message = "Request failed"
});
}
}
}
Code of my controller:
[Route("api/[controller]")]
[ApiController]
public class TestingController : ControllerBase
{
[HttpPost("/resource")]
public async Task<UserDto> testingResource( [FromBody] UserDto dto)
{
if (dto.email.Contains("hell"))
{
throw new CustomUPException(); //working
}
return dto;
}
}

Instead of using resource filter, I have used this strategy for formatting model validation errors as the documentation suggests.
Curious to know more about the raised issue though. Thanks in advance
// Add services to the container.
builder.Services.AddControllers().ConfigureApiBehaviorOptions(
options =>
{
options.InvalidModelStateResponseFactory = context =>
{
if (!context.ModelState.IsValid)
{
var data = new Dictionary<string, string?>();
//My Response formatter
var modelStateDictionary = context.ModelState;
foreach (var key in modelStateDictionary.Keys)
{
var errors = modelStateDictionary[key]?.Errors;
data.TryAdd(key, errors?[0].ErrorMessage);
}
return new ObjectResult(new UniversalResponseDto()
{
data = data,
statusCode = (int)HttpStatusCode.UnprocessableEntity,
sucess = false,
message = "One or more validation error occured"
})
{
StatusCode = (int)HttpStatusCode.UnprocessableEntity,
};
}
return new ObjectResult(context.HttpContext.Response);
};
});

Related

How to change HttpContext.Response

I want that my api all the time returns the same model something like this:
result {
body: "body",
error: {
message: "message",
StatusCode: "stasusCode"
}
}
My any controller returns some model. For example:
model: {
field1: "field1",
field2: "field2"
}
I want that a middleware generate the model like this if we have successful:
result {
body: {
field1: "field1",
field2: "field2"
}
error: null
}
So how I should change response body?
If we dont have successful I implemented like this:
var result = JsonSerializer.Serialize(new
{
response = new RequestModel
{
Error = new ErrorModel
{
Message = error.Message,
StatusCode = error.Code
},
Body= null
}
});
await response.WriteAsync(result);
Please help me...
What you are looking for is probably a custom error handling middleware. You can create something like below code to override response or have a look here to see how you can create it as you like.
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ErrorHandlingMiddleware> _logger;
public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception e)
{
_logger.LogWarning("message to log before handling the exception", e);
await HandleException(context, e);
}
}
}
private async Task HandleException(HttpContext context, Exception exception)
{
var responseObject = JsonSerializer.Serialize(new
{
response = new RequestModel
{
Error = new ErrorModel
{
Message = error.Message,
StatusCode = error.Code
},
Body= null
}
});
var exceptionData = var data = Encoding.UTF8.GetBytes(responseObject );
_logger.LogError(exception.ToString()); // log exception if you need
context.Response.ContentType = "application/json"; // set content type
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; // status code you want to return
await context.Response.Body.WriteAsync(exceptionData, 0, exceptionData.Length, CancellationToken.None);
}

How to fix 'A MessageReceived handler is blocking the gateway task.' in DiscordBot

I am trying to build my own Discord Bot, that soft bans people, who write racist or anti-Semitic words.
I try to do this with MessageReceivedAsync but it crashes all the time with the error 'A MessageReceived handler is blocking the gateway task.'
Here is the code of my Program.cs:
namespace NoNetworcc
{
class Program : ModuleBase<SocketCommandContext>
{
static void Main(string[] args)
=> new Program().MainAsync().GetAwaiter().GetResult();
public async Task MainAsync()
{
using (var services = ConfigureServices())
{
var client = services.GetRequiredService<DiscordSocketClient>();
client.Log += LogAsync;
client.MessageReceived += MessageReceivedAsync;
services.GetRequiredService<CommandService>().Log += LogAsync;
await client.LoginAsync(TokenType.Bot, "NzEyNDA2MDAxMjAxOTcxMjcw.XsRF4A.8YENNInx3D4kqJyK9N8xjTU3mcs");
await client.StartAsync();
await services.GetRequiredService<CommandHandlingService>().InitializeAsync();
await Task.Delay(Timeout.Infinite);
}
}
private Task LogAsync(LogMessage log)
{
Console.WriteLine(log.ToString());
return Task.CompletedTask;
}
private async Task MessageReceivedAsync(SocketMessage message)
{
using (BlacklistDatabaseContext lite = new BlacklistDatabaseContext())
{
var blacklistWords = lite.BlacklistWords;
foreach(var word in blacklistWords)
{
if(message.Content.Contains(word.Blacklistword.ToString()))
{
ulong roleID = 756500011331616840;
var role = Context.Guild.GetRole(roleID);
await ((IGuildUser)Context.User).AddRoleAsync(role);
await message.Channel.SendMessageAsync($"{Context.User} got softbanned for using the word '{word}'");
}
}
}
}
private ServiceProvider ConfigureServices()
{
return new ServiceCollection()
.AddSingleton<DiscordSocketClient>()
.AddSingleton<CommandService>()
.AddSingleton<CommandHandlingService>()
.AddSingleton<HttpClient>()
.AddSingleton<PictureService>()
.BuildServiceProvider();
}
}
}
And here is my Code for the HandlingService:
namespace NoNetworcc.Services
{
public class CommandHandlingService
{
private readonly CommandService _commands;
private readonly DiscordSocketClient _discord;
private readonly IServiceProvider _services;
public CommandHandlingService(IServiceProvider services)
{
_commands = services.GetRequiredService<CommandService>();
_discord = services.GetRequiredService<DiscordSocketClient>();
_services = services;
_commands.CommandExecuted += CommandExecutedAsync;
_discord.MessageReceived += MessageReceivedAsync;
}
public async Task InitializeAsync()
{
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
}
public async Task MessageReceivedAsync(SocketMessage rawMessage)
{
if (!(rawMessage is SocketUserMessage message)) return;
if (message.Source != MessageSource.User) return;
var argPos = 0;
var context = new SocketCommandContext(_discord, message);
await _commands.ExecuteAsync(context, argPos, _services);
}
public async Task CommandExecutedAsync(Optional<CommandInfo> command, ICommandContext context, IResult result)
{
if (!command.IsSpecified)
return;
if (result.IsSuccess)
return;
await context.Channel.SendMessageAsync($"error: {result}");
}
}
}
How can I fix this issue?
private Task MessageReceivedAsync(SocketMessage message) {
_ = Task.Run(async () => {
using (BlacklistDatabaseContext lite = new BlacklistDatabaseContext()) {
var blacklistWords = lite.BlacklistWords;
foreach (var word in blacklistWords) {
if(message.Content.Contains(word.Blacklistword.ToString())) {
ulong roleID = 756500011331616840;
var role = (message.Channel as ITextChannel)?.Guild.GetRole(roleID);
if (role != null) {
await (message.Author as SocketGuildUser)?.AddRoleAsync(role);
await message.Channel.SendMessageAsync($"{message.Author} got softbanned for using the word '{word}'");
}
}
}
}
});
return Task.CompletedTask;
}

Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object

After making an api call, if i input a wrong detail. My app keeps breaking with a null exception
I tried using the if-else to solve it. but it is still the same error
public class RemoteService
{
HttpClient httpClient;
public RemoteService()
{
httpClient = new HttpClient();
httpClient.BaseAddress = new Uri($"{App.BackendUrl}/");
}
public async Task<WeatherResponse> GetWeatherData(string query)
{
var weatherResponse = new WeatherResponse();
var response = await httpClient.GetAsync($"weather?q=" + query + App.AppID);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
weatherResponse = JsonConvert.DeserializeObject<WeatherResponse>(content);
weatherResponse.Error = false;
return weatherResponse;
}
else
{
//await Application.Current.MainPage.DisplayAlert("Error", "City not found", "OK");
return new WeatherResponse { Error = true };
}
}
}
The problem was actually from the viewmodel class. Solved

EnableRewind and leaveOpen on StreamReader are not stopping the request from being disposed

I'm using ApplicationInsights and I want to add the request, and after that the response, to the logging properties.
To achieve this I am implementing my own ITelemetryInitializer. It looks exactly like this.
public class MyInitializer : ITelemetryInitializer
{
private readonly IHttpContextAccessor _httpContextAccessor;
public MyInitializer(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry == null || _httpContextAccessor?.HttpContext?.Request == null
|| requestTelemetry.Properties.ContainsKey("RequestBody"))
{
return;
}
var request = _httpContextAccessor?.HttpContext?.Request;
request?.EnableRewind();
if (request.Method.Equals(HttpMethod.Post.ToString(), StringComparison.InvariantCultureIgnoreCase)
|| request.Method.Equals(HttpMethod.Put.ToString(), StringComparison.InvariantCultureIgnoreCase))
{
using (var reader = new StreamReader(request.Body, Encoding.UTF8, true, 1024, true))
{
var requestBody = reader.ReadToEnd();
requestTelemetry.Properties.Add("RequestBody", requestBody);
}
}
}
}
In startup I've added this
services.AddHttpContextAccessor();
services.AddSingleton<ITelemetryInitializer, MyInitializer>();
services.AddApplicationInsightsTelemetry();
The error I get is:
ObjectDisposedException: Cannot access a disposed object.
Object name: FileBufferingReadStream.
Microsoft.AspNetCore.WebUtilities.FileBufferingReadStream.ThrowIfDisposed()
I've used .EnableRewind as well as instructing the StreamReader to leave the file open. Despite this my request is still null when it actually hits my controller, or even when it hits my initializer again for a second pass (setting the response information).
Any suggestions are welcome.
Additionally I tried adding a piece of middleware to ensure .EnableRewind was on for everything, but this did nothing. I'd prefer not to have to add any additional middleware since I'd like there to be no other dependencies.
app.Use(async (context, next) =>
{
context.Request.EnableRewind();
await next();
});
Thanks.
As always the solution ends up being a single line of code. I owe Mr Gunnar Peipman a thanks for his blog post Reading request body in ASP.NET Core.
The line:
request.Body.Seek(0, SeekOrigin.Begin);
The code
public class MyInitializer : ITelemetryInitializer
{
private readonly IHttpContextAccessor _httpContextAccessor;
public MyInitializer(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry == null || _httpContextAccessor?.HttpContext?.Request == null
|| requestTelemetry.Properties.ContainsKey("RequestBody"))
{
return;
}
var request = _httpContextAccessor?.HttpContext?.Request;
request?.EnableRewind();
if (request.Method.Equals(HttpMethod.Post.ToString(), StringComparison.InvariantCultureIgnoreCase)
|| request.Method.Equals(HttpMethod.Put.ToString(), StringComparison.InvariantCultureIgnoreCase))
{
using (var reader = new StreamReader(request.Body, Encoding.UTF8, true, 1024, true))
{
var requestBody = reader.ReadToEnd();
request.Body.Seek(0, SeekOrigin.Begin);
requestTelemetry.Properties.Add("RequestBody", requestBody);
}
}
}
}

Stripe.net in Xamarin.Forms PCL with ASP.NET Core MVC Web API

I am trying to implement Stripe.net into my Xamarin.Forms PCL using an ASP.NET Core MVC Web API. The goal is to process credit card payment from users. My web API runs locally on http://localhost:port for testing purposes.
In the PaymentPage, a user enters their credit card information into Entry objects and when they click the submit Button, a method in the PaymentPageViewModel is called to start the logic:
async void OnFinishBookingClicked(object sender, System.EventArgs e)
{
// TODO: Stripe integration
var viewModel = (PaymentPageViewModel)this.BindingContext;
await viewModel.ProcessPayment();
}
This is part of the PaymentPageViewModel:
private readonly IStripeRepository _repository;
private readonly IAPIRepository _api;
public PaymentPageViewModel(IStripeRepository repository, IAPIRepository api)
{
_repository = repository;
_api = api;
}
public async Task ProcessPayment()
{
try
{
if (string.IsNullOrEmpty(ExpirationDate))
ExpirationDate = "09/18";
var exp = ExpirationDate.Split('/');
var token = _repository.CreateToken(CreditCardNumber, exp[0], exp[1], SecurityCode);
await Application.Current.MainPage.DisplayAlert("Test Message", token, "OK");
await _api.ChargeCard(token, 5.00M);
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Error", ex.Message, "OK");
}
}
This is what the APIRepository looks like:
public class APIRepository: IAPIRepository
{
const string Url = "http://localhost:5000";
private string authorizationKey;
private async Task<HttpClient> GetClient()
{
HttpClient client = new HttpClient();
if (string.IsNullOrEmpty(authorizationKey))
{
authorizationKey = await client.GetStringAsync(Url);
authorizationKey = JsonConvert.DeserializeObject<string>(authorizationKey);
}
client.DefaultRequestHeaders.Add("Authorization", authorizationKey);
client.DefaultRequestHeaders.Add("Accept", "application/json");
return client;
}
public async Task<string> ChargeCard(string token, decimal amount)
{
HttpClient client = await GetClient();
var json = JsonConvert.SerializeObject(new { token, amount });
var response = await client.PostAsync("/api/Stripe", new StringContent(json));
return await response.Content.ReadAsStringAsync();
}
}
The issue is that I get a series of errors during await _api.ChargeCard(token, 5.00M):
The first exception happens during authorizationKey = await client.GetStringAsync(Url); the exception message is the following:
{System.Net.Http.HttpRequestException: 404 (Not Found) at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode () [0x0000a] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.2.0.11/src/mono/mcs/class/System.Net.Http/System.Net.Http/HttpResponseM…}
I get another exception during response = await client.PostAsync("/api/Stripe", new StringContent(json));
{System.InvalidOperationException: The request URI must either be an absolute URI or BaseAddress must be set at System.Net.Http.HttpClient.SendAsync (System.Net.Http.HttpRequestMessage request, System.Net.Http.HttpCompletionOption completionOption, System.Thr…}
The third exception happens at the catch block of the viewModel.ProcessPayment() method:
{System.NullReferenceException: Object reference not set to an instance of an object at Zwaby.Services.APIRepository+d__3.MoveNext () [0x00184] in /Users/carlos/Projects/Zwaby/Zwaby/Services/APIRepository.cs:57 --- End of stack trace from previou…}
In my Web API project, I have a StripeController, but my implementation may not be fully correct:
[Route("api/Stripe")]
public class StripeController : Controller
{
private readonly StripeContext _context;
public StripeController(StripeContext context)
{
_context = context;
if (_context.StripeCharges.Count() == 0)
{
_context.StripeCharges.Add(new StripeItem { });
_context.SaveChanges();
}
}
[HttpGet]
public IActionResult Get(string key)
{
// TODO: implement method that returns authorization key
}
[HttpPost]
public IActionResult Charge(string stripeToken, decimal amount)
{
var customers = new StripeCustomerService();
var charges = new StripeChargeService();
var customer = customers.Create(new StripeCustomerCreateOptions
{
SourceToken = stripeToken
});
var charge = charges.Create(new StripeChargeCreateOptions
{
Amount = (int)amount,
Description = "Sample Charge",
Currency = "usd",
CustomerId = customer.Id
});
return View();
}
}
For completeness, I am including the StripeRepository class, the other parameter of the PaymentPageViewModel:
public class StripeRepository: IStripeRepository
{
public string CreateToken(string cardNumber, string cardExpMonth, string cardExpYear, string cardCVC)
{
StripeConfiguration.SetApiKey("my_test_key");
//TODO: Wireup card information below
var tokenOptions = new StripeTokenCreateOptions()
{
Card = new StripeCreditCardOptions()
{
Number = "4242424242424242",
ExpirationYear = 2018,
ExpirationMonth = 10,
Cvc = "123"
}
};
var tokenService = new StripeTokenService();
StripeToken stripeToken = tokenService.Create(tokenOptions);
return stripeToken.Id;
}
}
Thank you so much!

Resources