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);
}
Related
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);
};
});
I used Generic Repository in my sample project in Xamrin form to get data from api.
so my question is how can I make the following code Repository for soap webervice to become generic. so that the If you know of an example or a blog post please point me to the right direction
it is my Sample code:
public interface IGenericRepository
{
Task<T> GetAsync<T>(string uri, string authToken = "");
}
and impeliment interface:
public class GenericRepository: IGenericRepository
{
public async Task<T> GetAsync<T>(string uri, string authToken = "")
{
try
{
HttpClient httpClient = CreateHttpClient(uri);
string jsonResult = string.Empty;
var responseMessage = await Policy
.Handle<WebException>(ex =>
{
Debug.WriteLine($"{ex.GetType().Name + " : " + ex.Message}");
return true;
})
.WaitAndRetryAsync
(
5,
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
)
.ExecuteAsync(async () => await httpClient.GetAsync(uri));
if (responseMessage.IsSuccessStatusCode)
{
jsonResult =
await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
var json = JsonConvert.DeserializeObject<T>(jsonResult);
return json;
}
if (responseMessage.StatusCode == HttpStatusCode.Forbidden ||
responseMessage.StatusCode == HttpStatusCode.Unauthorized)
{
throw new ServiceAuthenticationException(jsonResult);
}
throw new HttpRequestExceptionEx(responseMessage.StatusCode, jsonResult);
}
catch (Exception e)
{
Debug.WriteLine($"{ e.GetType().Name + " : " + e.Message}");
throw;
}
}
}
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
No matter what I try I cannot seem to remove the ; charset=utf-8 part from my response's Content-Type header.
[HttpGet("~/appid")]
// Doesn't work
//[Produces("application/fido.trusted-apps+json")]
public string GetAppId()
{
// Doesn't work
Response.ContentType = "application/fido.trusted-apps+json";
// Doesn't work
//Response.ContentType = null;
//Response.Headers.Add("Content-Type", "application/fido.trusted-apps+json");
return JsonConvert.SerializeObject(new
{
foo = true
});
}
I always get application/fido.trusted-apps+json; charset=utf-8 when I only want application/fido.trusted-apps+json.
Note: This is to conform with the FIDO AppID and Facet Specification v1.0 for U2F which states:
The response must set a MIME Content-Type of "application/fido.trusted-apps+json".
I went with the following approach, using middleware to replace the header on the way out. Seems kinda hacky to have to use middleware like this:
Middleware
public class AdjustHeadersMiddleware
{
private readonly RequestDelegate _next;
public AdjustHeadersMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext, CurrentContext currentContext)
{
httpContext.Response.OnStarting((state) =>
{
if(httpContext.Response.Headers.Count > 0 && httpContext.Response.Headers.ContainsKey("Content-Type"))
{
var contentType = httpContext.Response.Headers["Content-Type"].ToString();
if(contentType.StartsWith("application/fido.trusted-apps+json"))
{
httpContext.Response.Headers.Remove("Content-Type");
httpContext.Response.Headers.Append("Content-Type", "application/fido.trusted-apps+json");
}
}
return Task.FromResult(0);
}, null);
await _next.Invoke(httpContext);
}
}
Startup.cs Configure()
app.UseMiddleware<AdjustHeadersMiddleware>();
I have found that you can use ContentResult to override this in your controller. So you could achieve what you want by doing the following for example
string bodyJson = JsonConvert.SerializeObject(new
{
foo = true
})
var response = new ContentResult()
{
Content = bodyJson,
ContentType = "application/fido.trusted-apps+json",
StatusCode = (int)System.Net.HttpStatusCode.OK,
};
return response;
If the system requesting your MVC endpoint sends a proper Accept: application/fido.trusted-apps+json, then I believe a custom formatter is what you're looking for.
See:
ASP.Net Core Custom Formatters (sample code)
Write Your Own ASP.NET Core MVC Formatters
It would look something like this (borrowed from the second link):
public class FidoTrustedAppOutputFormatter : IOutputFormatter
{
public FidoTrustedAppOutputFormatter
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/fido.trusted-apps+json"));
}
public bool CanWriteResult(OutputFormatterCanWriteContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.ContentType == null || context.ContentType.ToString() == "application/fido.trusted-apps+json")
return true;
return false;
}
public async Task WriteAsync(OutputFormatterWriteContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
var response = context.HttpContext.Response; response.ContentType = "application/fido.trusted-apps+json";
using (var writer = context.WriterFactory(response.Body, Encoding.UTF8))
{
// replace with Json.net implementation
Jil.JSON.Serialize(context.Object, writer);
await writer.FlushAsync();
}
}
}
public class FidoTrustedAppInputFormatter : IInputFormatter
{
public FidoTrustedAppInputFormatter
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/fido.trusted-apps+json"));
}
public bool CanRead(OutputFormatterCanWriteContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.ContentType == null || context.ContentType.ToString() == "application/fido.trusted-apps+json")
return true;
return false;
}
public Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
var request = context.HttpContext.Request; if (request.ContentLength == 0)
{
if (context.ModelType.GetTypeInfo().IsValueType)
return InputFormatterResult.SuccessAsync(Activator.CreateInstance(context.ModelType));
else return InputFormatterResult.SuccessAsync(null);
}
var encoding = Encoding.UTF8;//do we need to get this from the request im not sure yet
using (var reader = new StreamReader(context.HttpContext.Request.Body))
{
var model = Jil.JSON.Deserialize(reader, context.ModelType);
return InputFormatterResult.SuccessAsync(model);
}
}
}
Then register it in your startup:
services.AddMvcCore(options =>
{
options.InputFormatters.Insert(0, new FidoTrustedAppInputFormatter ());
options.OutputFormatters.Insert(0, new FidoTrustedAppOutputFormatter ());
});
I am trying to customize access token json object in my Web APi 2 project.
Which I have done successfully. But I am unable to figure out the case of failed request.
Here is my custom response:-
{ data: {}, success: 0, message: "failed" }
And following is a GrantResourceOwnerCredentials methon in ApplicationOAuthProvider which inherits OAuthAuthorizationServerProvider
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
ApplicationUserManager userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = userManager.Find(context.UserName, context.Password);
bool reqFailed = false;
string reqFailedMsg = "";
if (user == null)
{
reqFailed = true;
reqFailedMsg = "The user name or password is incorrect.";
}
if (!reqFailed && !user.EmailConfirmed)
{
reqFailed = true;
reqFailedMsg = "The user email is not confirmed.";
//context.SetError("invalid_grant", "The user email is not confirmed.");
//return;
}
if (!reqFailed && !user.IsActive)
{
reqFailed = true;
reqFailedMsg = "The user is disabled.";
//context.SetError("invalid_grant", "The user is disabled.");
//return;
}
if (reqFailed)
{
try
{
context.Response.Headers.Add(LMS.Utilities.Constants.MyMiddlewareHeader, new[] { (400).ToString() });
context.Response.Headers.Add(LMS.Utilities.Constants.MyMiddlewareHeaderMsg, new[] { reqFailedMsg });
}
catch (Exception ex)
{
}
return;
}
var form = await context.Request.ReadFormAsync();
if (form["deviceType"] != null && form["deviceToken"] != null)
{
user.DeviceToken = form["deviceToken"];
user.DeviceType = form["deviceType"];
userManager.Update(user);
}
ClaimsIdentity oAuthIdentity = userManager.GenerateUserIdentity(userManager, user,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = userManager.GenerateUserIdentity(userManager, user,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
//context.Request.Context.Authentication.SignIn(cookiesIdentity);
context.Request.Context.Authentication.SignIn(properties, cookiesIdentity);
//return Task.FromResult<object>(null);
}
And middleware class:-
public class AuthenticationMiddleware : OwinMiddleware
{
public AuthenticationMiddleware(OwinMiddleware next) : base(next) {
}
public override async Task Invoke(IOwinContext context)
{
await Next.Invoke(context);
if (context.Response.Headers.ContainsKey(Constants.MyMiddlewareHeader))
{
using (var reader = new StreamReader(context.Request.Body))
{
var headerValues = context.Response.Headers.GetValues(Constants.MyMiddlewareHeader);
context.Response.StatusCode = Convert.ToInt16(headerValues.FirstOrDefault());
context.Response.Headers.Remove(Constants.MyMiddlewareHeader);
var headerValuesMessage = context.Response.Headers.GetValues(Constants.MyMiddlewareHeaderMsg);
string msg = headerValuesMessage.FirstOrDefault().ToString();
context.Response.Headers.Remove(Constants.MyMiddlewareHeaderMsg);
string resp = Newtonsoft.Json.JsonConvert.SerializeObject(new ResponseViewModel<object>(new { }, (int)ResponseStatus.Fail, msg));
var response = context.Response;
var body = await reader.ReadToEndAsync();
var bytes = Encoding.UTF8.GetBytes(resp);
response.ContentLength = bytes.Length;
await response.WriteAsync(bytes);
return;
}
}
}
}
What I am getting in respose is:-
{"error":"invalid_grant"}{"Data":{},"Status":0,"Message":"The user name or
Which is invalid json as you can see.
Any help would be appreciated.
Thanks!