Any way we can override data annotation response in asp.net core 3.1 - data-annotations

i have .net core 3.1 web api project.
I have put some data annottion validation on my models. my response of validation is coming like
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|41a37de5-4212fb57b7a19a61.",
"errors": {
"fk_entity_id": [
"Please enter a value bigger than 0"
]
}
}
any way i can overridde that response like
{
"success": "failed",
"message": "validation error occured",
"errors": {
"fk_entity_id": [
"Please enter a value bigger than 0"
]
}
}
find one article but it seems to be related to asp.net core older version
https://www.c-sharpcorner.com/blogs/customizing-model-validation-response-resulting-as-http-400-in-net-core
I have tried following but still same response
private void CustomValidationResponse(IServiceCollection services)
{
services.Configure<ApiBehaviorOptions>(
options => options.InvalidModelStateResponseFactory = actionContext =>
{
return CustomErrorResponse(actionContext);
}
);
}
private BadRequestObjectResult CustomErrorResponse(ActionContext actionContext)
{
var errorRecordList = actionContext.ModelState
.Where(modelError => modelError.Value.Errors.Count > 0)
.Select(modelError => new Error
{
ErrorField = modelError.Key,
ErrorDescription = modelError.Value.Errors.FirstOrDefault().ErrorMessage
}).ToList();
return new BadRequestObjectResult(new
{
success = "failed",
message = "Validation error occured",
errors = errorRecordList
});
}

Problem was placement of CustomValidationResponse inside
public void ConfigureServices(IServiceCollection services)
When i added it in last it worked

Related

How to customize the built-in MVC validation response format?

I'm using my own middleware to capture exceptions thrown in my API to format the response to the client. This includes things like checking for the dev env to send additional information and logging. This all works great but the built-in validation middleware responds with a different response format. I want to keep the functionality and just change what data is sent to the client and how it's formatted.
Currently it returns the default
{
"message": "Validation error(s)",
"details": [
"The value '35353535353535353535353535353535353535353535' is not valid."
]
}
You can customize the default response by using a BadResultObject in the InvalidaModelStateResponseFactory of the ApiBehaviorOptions class. As an example:
apiBehaviorOptions.InvalidModelStateResponseFactory = actionContext => {
return new BadRequestObjectResult(new {
Code = 400,
Request_Id = "Someuniqueid",
Messages = actionContext.ModelState.Values.SelectMany(x => x.Errors)
.Select(x => x.ErrorMessage)
});
Configured:
serviceCollection.PostConfigure<ApiBehaviorOptions>(apiBehaviorOptions =>
apiBehaviorOptions.InvalidModelStateResponseFactory = ...
);
Or you can send the response directly from the action you are using as well with your own custom validation error result class. For example:
public class ValidationError
{
[JsonProperty(NullValueHandling=NullValueHandling.Ignore)]
public string Field { get; }
public string Message { get; }
public ValidationError(string field, string message)
{
Field = field != string.Empty ? field : null;
Message = message;
}
}
public class ValidationResultModel
{
public string Message { get; }
public List<ValidationError> Errors { get; }
public ValidationResultModel(ModelStateDictionary modelState)
{
Message = "Validation Failed";
Errors = modelState.Keys
.SelectMany(key => modelState[key].Errors.Select(x => new
ValidationError(key, x.ErrorMessage)))
.ToList();
}
}
Then we can create our own IActionResult. Here:
public class ValidationFailedResult : ObjectResult
{
public ValidationFailedResult(ModelStateDictionary modelState)
: base(new ValidationResultModel(modelState))
{
StatusCode = StatusCodes.Status404...;
}
}
And update our ValidateModelAttribute by overriding the OnActionExecuting to perform actions before they are taken.
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new ValidationFailedResult(context.ModelState);
}
}
}
Sources:
Customize automatic response on validation error
https://www.jerriepelser.com/blog/validation-response-aspnet-core-webapi/

Get size or length of json array response (restAssured Response interface)

We have REST API automation scripts using RestAssured. In this declared response object as public static Response response; and retrieving the response data using response.jsonPath().get("id"), during this trying to even get the size or length of the id, even need to get details about tags array.
JSON Response:
[
{
"id": 1,
"name": "test1",
"tags": [
{
"tagType": "details1",
"tag": {
"description": null
}
}
]
},
{
"id": 2,
"name": "test2",
"tags": [
{
"tagType": "details2",
"tag": {
"description": null
}
}
]
}
]
Tried below ways:
public static Response response;
List<String> resIDs = response.jsonPath().get("id");
System.err.println("Retrieved IDs from Response: " + resIDs);
O/P: is [1,2,3,4,5,6,7]
Tried as resIDs.size(), that also no response printed.
List<Object> size = response.jsonPath().getList("$");
System.err.println("ArraySize for IDs from Response: " + size);
or
int size = response.jsonPath().getList("$").size();
O/P: Not printed/nothing shown
Please guide how to get the size/length.
I don't seem to find any issue in your code, I just changed a bit to run locally and its working fine. Here's my code
public class S_62591968 {
public static Response postCallWithJsonBodyParam(String URL) {
return RestAssured.given().relaxedHTTPSValidation().contentType(ContentType.JSON).request().when().get(URL);
}
public static void main(String[] args) {
String url_endPoint = "http://localhost:8089/def/abc";
Response response = postCallWithJsonBodyParam(url_endPoint);
List<String> resIDs = response.jsonPath().get("id");
System.out.println("Retrieved IDs from Response : " + resIDs);
System.out.println("ArraySize for IDs from Response : " + resIDs.size());
}
}
Console :
Retrieved IDs from Response : [1, 2]
ArraySize for IDs from Response : 2

How to setup Swashbuckle.AspNetCore and Oauth2

I'm trying to figure out where I went wrong.
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Info { Title = "MySite API", Version = "v1" });
options.OperationFilter<AuthorizeCheckOperationFilter>();
options.OperationFilter<AddSwaggerHeadersOperationFilter>();
options.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = "authorization url",
TokenUrl = "token url",
Scopes = new Dictionary<string, string>()
{
{ "scope", "Scope" }
}
});
});
//Configure Method
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "MySite API V1");
options.OAuthClientId("MyClientId");
options.OAuthAppName("Swagger Api Calls");
//c.RoutePrefix = string.Empty;
});
//AuthorizeCheckOperationFilter
internal class AuthorizeCheckOperationFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
if (context.ApiDescription.TryGetMethodInfo(out var methodInfo))
{
var attributes = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true);
if (attributes.OfType<AuthorizeAttribute>().Any())
{
operation.Responses.Add("401", new Response { Description = "Unauthorized" });
operation.Responses.Add("403", new Response { Description = "Forbidden" });
operation.Security = new List<IDictionary<string, IEnumerable<string>>>();
operation.Security.Add(new Dictionary<string, IEnumerable<string>>
{
{ "oauth2", new [] { "api1" } }
});
}
}
}
}
//Extra field
internal class AddSwaggerHeadersOperationFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
if (operation.Parameters == null)
operation.Parameters = new List<IParameter>();
operation.Parameters.Add(new NonBodyParameter
{
Name = "SomeField",
In = "header",
Type = "string",
Required = true,
Default = "some value"
});
}
}
Now when I open up the swagger page I get the Authorize button, to which I click and when I fill out the details there I get redirected to my Identity Website which logs me in and redirects right back to swagger. Swagger then says authorized, as if everything is fine.
Then I try to use an API which requires Bearer token to be passed and it doesn't pass it. I don't see it in the header and by my logs from the identity website nothing was passed.
Any idea why or how to fix this? I'm using Swashbuckle.AspNetCore 4.1 package.
You can add DocumentFilter :
public class SecurityRequirementsDocumentFilter : IDocumentFilter
{
public void Apply(SwaggerDocument document, DocumentFilterContext context)
{
document.Security = new List<IDictionary<string, IEnumerable<string>>>()
{
new Dictionary<string, IEnumerable<string>>()
{
{ "oauth2", new string[]{ "openid", "profile", "email" } },
}
};
}
}
And then register the filter in AddSwaggerGen function :
options.DocumentFilter<SecurityRequirementsDocumentFilter>();
Reference : https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/603#issuecomment-368487641
I test with your code sample and it works as expected :

Adding x-logo vendor extension using Swashbuckle Asp.Net Core for ReDoc

I'm using swagger.json file (generated by Swashbuckle) for ReDoc to display API documentation.
What I Need:
Add x-logo vendor extension to swagger json generated using Swashbuckle (Swashbuckle.AspNetCore.SwaggerGen library) so that ReDoc UI shows logo at the top left corner like this
Problem:
I was able to add x-log to the swagger.json file but it is added to wrong section of the file. It needs to be inside info section.
This is what I have done to add the x-logo
Created a document filter like below
public class XLogoDocumentFilter : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
swaggerDoc.Extensions["x-logo"] = new { url = "https://URL/of/the/logo", altText = "Company Logo" };
}
}
Added the filter to SwaggerDoc as
services.AddSwaggerGen(options =>
{
options.DocumentFilter<XLogoDocumentFilter>();
});
Actual
{
"swagger": "2.0",
"info": {
"version": "v1",
"title":"Sample REST API"
},
"x-logo": {
"url": "https://rebilly.github.io/ReDoc/petstore-logo.png",
"altText": "Aimia Logo"
}
}
Expected
{
"swagger": "2.0",
"info": {
"version": "v1",
"title":"Sample REST API",
"x-logo": {
"url": "https://rebilly.github.io/ReDoc/petstore-logo.png",
"altText": "Aimia Logo"
}
},
}
Really appreciate any help or suggestions to have the x-logo in the correct section of the swagger.json file.
After typing the question I found the solution myself. Instead of adding extension directly to swaggerDoc, add it to swaggerDoc.Info object.
public class XLogoDocumentFilter : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
// need to check if extension already exists, otherwise swagger
// tries to re-add it and results in error
if (!swaggerDoc.Info.Extensions.ContainsKey("x-logo"))
{
swaggerDoc.Info.Extensions.Add("x-logo", new {
url = "https://URL/To/The/Logo",
altText = "Logo",
});
}
}
}
The newer versions of Swashbuckle support this in the SwaggerDoc setup:
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = ApiDescription,
Version = "v1",
Extensions = new Dictionary<string, IOpenApiExtension>
{
{"x-logo", new OpenApiObject
{
{"url", new OpenApiString("https://blah.com/logo")},
{ "altText", new OpenApiString("The Logo")}
}
}
}
});
for .NET core 2.2 and higher
public class XLogoDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
// need to check if extension already exists, otherwise swagger
// tries to re-add it and results in error
if (!swaggerDoc.Info.Extensions.ContainsKey("x-logo"))
swaggerDoc.Info.Extensions.Add("x-logo", new OpenApiObject
{
{"url", new OpenApiString("https://www.petstore.com/assets/images/logo.png")},
{"backgroundColor", new OpenApiString("#FFFFFF")},
{"altText", new OpenApiString("PetStore Logo")}
});
}
}

Google Site Verification API

I am trying to develop Google site verification system using ASP.Net. Also using the Google explorer (https://developers.google.com/site-verification/v1/webResource/insert) to test the request method such as JSON and HTTP request format.
This is what I am sending to the Google.
POST https://www.googleapis.com/siteVerification/v1/webResource?verificationMethod=site&key={YOUR_API_KEY}
Content-Type: application/json
Authorization: Bearer xxxxxxxxxxxxxxx
X-JavaScript-User-Agent: Google APIs Explorer
{
"id": "myid",
"owners": [
"development#gmail.com"
],
"site": {
"type": "site",
"identifier": "http://www.example.net/"
}
}
I am getting following response from the Google.
{
"error": {
"errors": [
{
"domain": "global",
"reason": "backendError",
"message": "Backend Error"
}
],
"code": 503,
"message": "Backend Error"
}
}
>
IAuthorizationState authorization;
protected void Page_Load(object sender, EventArgs e)
{
if (googleClient != null)
{
if (IsPostBack)
{
authorization = googleClient.ProcessUserAuthorization();
if (authorization != null)
{
this.AccessToken = authorization.AccessToken;
}
else if (this.AccessToken == null)
{
googleClient.RequestUserAuthorization(scope: new[] { GoogleClient.Scopes.WebMaster.SiteVerification });
}
}
}
}
protected void Button1_Click(object sender, EventArgs e)
{
if (authorization != null)
{
IOWebMasterInsertGraph webMasterInsertGraph = googleClient.RequestForVerification(authorization);
}
}
public IOWebMasterInsertGraph RequestForVerification(IAuthorizationState authState)
{
if ((authState != null) && (authState.AccessToken != null))
{
WebRequest request = WebRequest.Create("https://www.googleapis.com/siteVerification/v1/webResource?verificationMethod=site");
string path = HostingEnvironment.MapPath(#"~/App_Data/GoogleInsert.json");
MemoryStream ms = new MemoryStream();
FileStream fileStreem = new FileStream(path, FileMode.Open, FileAccess.Read);
byte[] bytes = new byte[fileStreem.Length];
fileStreem.Read(bytes, 0, (int)fileStreem.Length);
ms.Write(bytes, 0, (int)fileStreem.Length);
request.ContentType = "application/json";
request.Method = "POST";
request.ContentLength = ms.Length;
ms.Seek(0, SeekOrigin.Begin);
using (Stream requestStream = request.GetRequestStream())
{
ms.CopyTo(requestStream);
}
WebResponse response = request.GetResponse();
if (response != null)
{
Stream responseStream = response.GetResponseStream();
if (responseStream != null)
{
//return GoogleGraph.Deserialize(responseStream);
return WebMasterInsertGraph.Deserialize(responseStream);
}
}
}
return null;
}
Does anyone know the reason for this?
Why don't you use the Google .NET client library for Site verification?
You even have a sample code for that.
Try it out...
I have found the answer myself.
The following has to be sent to the GoogleApi without the id in JSON. The verificationMethos has to be one you selected when getting token.
POST /siteVerification/v1/webResource?verificationMethod=file HTTP/1.1 Host: www.googleapis.com
Content-length: 138
Content-type: application/json
Authorization: Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
{
"owners": [
"sample#gmail.com" ],
"site": {
"identifier": "http://test.sample.com/",
"type": "SITE" }
}

Resources