I am using .Net framework 4.6.1 and Swashbuckle version 5.3.2 in my WebApi project. Swagger UI is not giving an option to send the input as a request body to my POST Api which uses a custom model binder.
- Model Used :
[ModelBinder(typeof(FieldValueModelBinder))]
public class Employee
{
public int EmployeeID { get; set; }
public string EmployeeName { get; set; }
public string City { get; set; }
}
- API Post method used:
[HttpPost]
// POST: api/Employee
public HttpResponseMessage Post([ModelBinder(typeof(FieldValueModelBinder))]Employee emp)
{
if (!ModelState.IsValid)
return Request.CreateResponse(HttpStatusCode.BadRequest, "Please provide valid input");
else
//Add Employee logic here
return Request.CreateResponse(HttpStatusCode.OK, "Employee added sucessfully");
}
- Model Binder used :
public class FieldValueModelBinder : System.Web.Http.ModelBinding.IModelBinder
{
/// <summary>
/// Store received data in API in KeyValuePair
/// </summary>
private List<KeyValuePair<string, string>> kvps;
/// <summary>
/// Storing error while binding data in Model class
/// </summary>
private Dictionary<string, string> dictionaryErrors = new Dictionary<string, string>();
/// <summary>
/// Implementing Base method and binding received data in API to its respected property in Model class
/// </summary>
/// <param name="actionContext">Http Action Context</param>
/// <param name="bindingContext">Model Binding Context</param>
/// <returns>True if no error while binding. False if any error occurs during model binding</returns>
public bool BindModel(HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext)
{
try
{
var bodyString = actionContext.Request.Content.ReadAsStringAsync().Result;
if (actionContext.Request.Method.Method.ToUpper().Equals("GET"))
{
var uriContext = HttpUtility.ParseQueryString(actionContext.Request.RequestUri.Query);
if (uriContext.HasKeys())
{
this.kvps = uriContext.AllKeys.ToDictionary(k => k, k => uriContext[k]).ToList<KeyValuePair<string, string>>();
}
}
else if (!string.IsNullOrEmpty(bodyString))
{
this.kvps = this.ConvertToKvps(bodyString);
}
else
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Please provide valid input data.");
return false;
}
}
catch (Exception ex)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Please provide data in a valid format.");
return false;
}
// Initiate primary object
var obj = Activator.CreateInstance(bindingContext.ModelType);
try
{
this.SetPropertyValues(obj);
}
catch (Exception ex)
{
if (this.dictionaryErrors.Any())
{
foreach (KeyValuePair<string, string> keyValuePair in this.dictionaryErrors)
{
bindingContext.ModelState.AddModelError(keyValuePair.Key, keyValuePair.Value);
}
}
else
{
bindingContext.ModelState.AddModelError("Internal Error", ex.Message);
}
this.dictionaryErrors.Clear();
return false;
}
// Assign completed Mapped object to Model
bindingContext.Model = obj;
return true;
}
I am facing below issues:
When we use ‘ModelBinder’ in our post method, Swagger UI is
displaying this screen where the input parameter are posted in a
query string and CustomModelBinder is invoked and tries to read
request body to perform model binding and validation and gets null in
this case.
Public HttpResponseMessage Post([ModelBinder(typeof(FieldValueModelBinder))]Employee emp)
When we use ‘FromBody’ in our post method, Swagger UI displays this
screen where we can send the input in a request body, but in this
case CustomModelBinder is not invoked and we are not able to perform
modelbinding and validation.
public HttpResponseMessage Post([FromBody]Employee emp)
When we try using both ‘modelbinder’ and ‘frombody’, Swagger UI takes
the input as a query and we get the below response:
Tried with Postman, the API works fine and we are able to pass the input in request body and get the proper output. The custom model binding also works and populates the error message in case of invalid model state and we can then use those messages to send in the response.
What needs to be changed to invoke the custom model binder from Swagger UI while posting input data to API in request body. Please Suggest.
You can do that with an IDocumentFilter here is the code:
private class ApplyDocumentVendorExtensions : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry s, IApiExplorer a)
{
if (swaggerDoc != null)
{
foreach (var path in swaggerDoc.paths)
{
if (path.Value.post != null && path.Value.post.parameters != null )
{
var parameters = path.Value.post.parameters;
if (parameters.Count == 3 && parameters[0].name.StartsWith("emp"))
{
path.Value.post.parameters = EmployeeBodyParam;
}
}
}
}
}
private IList<Parameter> EmployeeBodyParam
{
get
{
return new List<Parameter>
{
new Parameter {
name = "emp",
#in = "body",
required = true,
schema = new Schema {
#ref = "#/definitions/Employee"
}
}
};
}
}
}
Related
I am new for using asp net web api.
I want to create UploadDocument feature in my web api.
so, I create this way.
From Client
api.post('vendor/UploadDocument', formData, { headers: { 'Content-Type': 'multipart/form-data' } })
Controller
public class VendorController : ApiController {
[HttpPost]
public HttpResponseMessage UploadDocument()
{
try
{
if (!Request.Content.IsMimeMultipartContent())
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
var request = HttpContext.Current.Request;
var model = new UploadDocumentViewModel();
model.field1 = request.Form["field1"];
model.field2 = request.Form["field2"];
model.Document = request.Files["Document"];
if (ModelState.IsValid)
{
return Request.CreateResponse(HttpStatusCode.OK);
}
else //ModelState is not valid
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
catch (Exception exception)
{
return Request.CreateResponse(HttpStatusCode.InternalServerError);
}
}
}
Model
public class UploadDocumentViewModel
{
[Required]
public string field1 { get; set; }
[Required]
public int field2 { get; set; }
[Required]
public HttpPostedFile Document { get; set; }
}
My problem is, in controller ModelState always empty.
I have tried to add code
Validate(model);
if (ModelState.IsValid)...
but it didn't work too.
can someone have any idea for validating model data annotation in multipart form data ?
try clear model state before validate
ModelState.Clear();
this.Validate(model);
if (ModelState.IsValid) {
}
Check this answer : Custom DataAnnotation
Your method should look like this:
public class VendorController : ApiController {
[HttpPost]
public IHttpActionResult UploadDocument(UploadDocumentViewModel viewModel)
{
try
{
if (!Request.Content.IsMimeMultipartContent())
return StatusCode(HttpStatusCode.UnsupportedMediaType);
if (viewNodel == null)
return BadRequest("Model is empty");
var field1 = viewModel.field1;
var field2 = viewModel.field2;
var documents = viewModel.document;
if (ModelState.IsValid)
{
return Ok();
}
else
{
return BadRequest(ModelState);
}
}
catch (Exception exception)
{
return InternalServerError(exception);
}
}
}
I prefer to passing some of those validations in action filters, to make your methods cleaner (try/catch, modelstate).
If you will have some problems with model binding, you can implement your custom model binder.
I used Stormpath - Token Authentication in ASP.NET Core solution to generate JWT with my logic. Now I need to validate the token if the client (moblie app) sends it back to me.
Any idea how?
Thnx
ASP .NET MVC allows you to create your own authentication mechanism as action filter.
For example:
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Filters;
using System.Web.Http.Results;
using WebAPI.SignatureTypes;
namespace WebAPI.ActionFilters
{
/// <summary>
/// Add this attribute to a controller or action to enforce token-based authentication
/// </summary>
public class TokenRequired : Attribute, IAuthenticationFilter
{
#region Static members for configurations and initialization of reusable components
static TokenRequired()
{
TokenValidator = new JwtSecurityTokenHandler();
Trace.Assert(Realm == null, "The realm should be set by the WebApiConfig class.");
Trace.Assert(TokenValidations == null, "The token validation parameters should be initialized by the Use() function.");
}
public static Uri Realm { get; internal set; }
public static TokenValidationParameters TokenValidations { get; internal set; }
// NuGet: Install-Package System.IdentityModel.Tokens.Jwt
public static ISecurityTokenValidator TokenValidator { get; internal set; }
internal static void Use(Type hashType)
{
var map = new Dictionary<Type, TokenValidationParameters>
{
[typeof(HMACSHA256)] = HmacSignatureFactory.ValidationParameters
};
TokenValidationParameters validations = null;
if (!map.TryGetValue(hashType, out validations))
{
throw new NotSupportedException($"Hashing algorithm of type '{hashType}' is not supported.");
}
TokenValidations = validations;
}
#endregion
/// <summary>
/// Only one instance of this attribute can be applied to a single class or function
/// </summary>
public bool AllowMultiple
{
get
{
return false;
}
}
/// <summary>
/// Deny access if a token is missing from the header Authorization, or invalid; otherwise, let the request goes through.
/// </summary>
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
Trace.Assert(TokenValidator != null, "TokenValidator is required for authentication");
Trace.Assert(TokenValidations != null, "TokenValidations are required for authentication");
AuthenticationHeaderValue authentication = null;
SecurityToken securityToken = null;
try
{
authentication = context.Request.Headers.Authorization;
context.Principal = TokenValidator.ValidateToken(authentication.Parameter, TokenValidations, out securityToken);
}
catch (Exception error)
{
Trace.TraceError($"Missing or invalid token. Error: {error}");
}
if (authentication == null || authentication.Scheme != "Bearer" || securityToken == null)
{
context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], context.Request);
}
return Task.FromResult(0);
}
/// <summary>
/// Return a challenge response with the realm included in the header WWW-Authenticate
/// </summary>
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
context.Result = new ChallengeResult(context.Result, Realm);
return Task.FromResult(0);
}
/// <summary>
/// The challenge response to unauthenticated requests
/// </summary>
private class ChallengeResult : IHttpActionResult
{
private IHttpActionResult contextResult;
private Uri realm;
public ChallengeResult(IHttpActionResult contextResult, Uri realm)
{
this.contextResult = contextResult;
this.realm = realm;
}
public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = await contextResult.ExecuteAsync(cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Bearer", $"realm=\"{realm}\""));
return response;
}
}
}
}
On an Web API controller I have the following:
if (!ModelState.IsValid)
return BadRequest();
BadRequest: https://msdn.microsoft.com/en-us/library/system.web.http.apicontroller_methods%28v=vs.118%29.aspx
I would like to create a custom error message to pass errors as json.
Something like:
if (!ModelState.IsValid)
return ModelHasErrors(errors);
How can I create a custom error response?
The simplest way is
return Request.CreateErrorResponse(HttpStatusCode.NotFound, ModelState);
I am used to use special object, that represents error response. I can add my specific (translated) message, status code, etc.
public class ErrorModel
{
public ErrorModel(HttpStatusCode statusCode, string message)
{
StatusCode = (int)statusCode;
Message = message;
ValidationErrors = new Dictionary<string, ModelErrorCollection>();
}
public ErrorModel(HttpStatusCode statusCode)
{
StatusCode = (int)statusCode;
ValidationErrors = new Dictionary<string, ModelErrorCollection>();
}
public string Message { get; set; }
public int StatusCode { get; set; }
public Dictionary<string, ModelErrorCollection> ValidationErrors { get; set; }
public Exception Exception { get; set; }
}
Then I have extension for CreateCustomResponse
public static class ApiExtensions
{
public static HttpResponseMessage CreateCustomResponse(this HttpRequestMessage request, HttpStatusCode statusCode, string errorMessage)
{
var errorMessageModel = new ErrorModel(statusCode, errorMessage);
return request.CreateResponse(statusCode, errorMessageModel);
}
public static HttpResponseMessage CreateCustomResponse(this HttpRequestMessage request, HttpStatusCode statusCode, Exception exception, string errorMessage = "")
{
if (string.IsNullOrEmpty(errorMessage) && exception != null)
{
errorMessage = exception.Message;
}
var errorMessageModel = new ErrorModel(statusCode, errorMessage)
{
Exception = exception
};
return request.CreateResponse(statusCode, errorMessageModel);
}
public static HttpResponseMessage CreateCustomResponse(this HttpRequestMessage request,
HttpStatusCode statusCode, ModelStateDictionary modelState, string errorMessage = "")
{
if (string.IsNullOrEmpty(errorMessage))
{
errorMessage = ApiValidationMessages.GeneralModelIsNotValid;
}
var errorMessageModel = new ErrorModel(statusCode, errorMessage);
foreach (var error in modelState.Where(x => x.Value.Errors != null && x.Value.Errors.Any()))
{
errorMessageModel.ValidationErrors.Add(error.Key.Replace("model.", ""), error.Value.Errors);
}
return request.CreateResponse(statusCode, errorMessageModel);
}
}
And finally in my controllers I just call:
return Request.CreateCustomResponse(HttpStatusCode.NotFound, ApiHttpResultMessages.NotFound);
You can find inspiration in my CodePlex project Web API Design: https://webapidesign.codeplex.com/
You can return directly whichever object you want, and it will be serialized as JSON. It can even be an anonymous class object created with new { }
On The client side you have to check if you've received a regurlar response, or the error object, which can be easyly donde by checking the existence of some property of your custom error object.
You could return a HttpResponseMessage using an object containing your error messages (in this example errors) as content:
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new ObjectContent(typeof(ErrorClass), errors, new JsonMediaTypeFormatter())
};
More information about how to return action results can be found here.
You can obviously reuse the above code by creating a method and call it like in your example: ModelHasErrors(errors).
However, if you often find yourself returning the same response, a nice option would be to create an custom exception filter which would return the same response whenever ModelState.IsValid is false.
I'm running ASP WebAPI 2 and successfully installed Swashbuckle. I am trying to figure out how one defines what the default schema values are?
For example, on the Swagger live demo site they changed the default value of pet to "doggie". They also defined the allowable values for status. (Live Demo)
I managed to get this working by following what's on this link:
https://github.com/domaindrivendev/Swashbuckle/issues/69#issuecomment-53953785
In short this is what needs to be done:
Create the classes SwaggerDefaultValue and AddDefaultValues as described in the link. Some changes that I did:
public class SwaggerDefaultValue : Attribute
{
public string Name { get; set; }
public string Value { get; set; }
public SwaggerDefaultValue(string value)
{
this.Value = value;
}
public SwaggerDefaultValue(string name, string value) : this(value)
{
this.Name = name;
}
}
public class AddDefaultValues : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
IDictionary<string, object> parameterValuePairs =
GetParameterValuePairs(apiDescription.ActionDescriptor);
foreach (var param in operation.parameters)
{
var parameterValuePair = parameterValuePairs.FirstOrDefault(p => p.Key.IndexOf(param.name, StringComparison.InvariantCultureIgnoreCase) >= 0);
param.#default = parameterValuePair.Value;
}
}
private IDictionary<string, object> GetParameterValuePairs(HttpActionDescriptor actionDescriptor)
{
IDictionary<string, object> parameterValuePairs = new Dictionary<string, object>();
foreach (SwaggerDefaultValue defaultValue in actionDescriptor.GetCustomAttributes<SwaggerDefaultValue>())
{
parameterValuePairs.Add(defaultValue.Name, defaultValue.Value);
}
foreach (var parameter in actionDescriptor.GetParameters())
{
if (!parameter.ParameterType.IsPrimitive)
{
foreach (PropertyInfo property in parameter.ParameterType.GetProperties())
{
var defaultValue = GetDefaultValue(property);
if (defaultValue != null)
{
parameterValuePairs.Add(property.Name, defaultValue);
}
}
}
}
return parameterValuePairs;
}
private static object GetDefaultValue(PropertyInfo property)
{
var customAttribute = property.GetCustomAttributes<SwaggerDefaultValue>().FirstOrDefault();
if (customAttribute != null)
{
return customAttribute.Value;
}
return null;
}
}
Edit your SwaggerConfig and add the AddDefaultValues class to the OperationFilters:
GlobalConfiguration.Configuration
.EnableSwagger(c => {
...
c.OperationFilter<AddDefaultValues>()
...
});
Now for the parameters I want default values I just add the following:
public IHttpActionResult Put([FromBody]Pet pet)
{
...
return Ok();
}
public class Pet {
[SwaggerDefaultValue("doggie")]
public string Name { get; set; }
[SwaggerDefaultValue("available")]
public string Status;
...
}
Well the code of vgaspar.trivix did not work completly for me, the default values did not get set for the schema. Also i got an NullPointerException. I managed to get it working as intended by editing the Apply method and manipulated the schemaRegistry like this:
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.parameters == null)
return;
IDictionary<string, object> parameterValuePairs =
GetParameterValuePairs(apiDescription.ActionDescriptor);
foreach (var param in operation.parameters)
{
if (param.schema != null && param.schema.#ref != null)
{
string schemaName = param.schema.#ref.Split('/').LastOrDefault();
if (schemaRegistry.Definitions.ContainsKey(schemaName))
foreach (var props in schemaRegistry.Definitions[schemaName].properties)
{
if (parameterValuePairs.ContainsKey(props.Key))
props.Value.#default = parameterValuePairs[props.Key];
}
}
var parameterValuePair = parameterValuePairs.FirstOrDefault(p => p.Key.IndexOf(param.name, StringComparison.InvariantCultureIgnoreCase) >= 0);
param.#default = parameterValuePair.Value;
}
}
An example Model Schema can be defined by implementing ISchemaFilter and registering it using the following:
httpConfig
.EnableSwagger(c =>
{
c.SchemaFilter<AddSchemaExamples>()
});
An example implementation is provided here:
public class AddSchemaExamples : ISchemaFilter
{
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
if (type == typeof(Product))
{
schema.example = new Product
{
Id = 123,
Type = ProductType.Book,
Description = "Treasure Island",
UnitPrice = 10.0M
};
}
}
}
Source: https://github.com/domaindrivendev/Swashbuckle/issues/162
I know this thread is quite old, but I wanted to share my solution which creates a custom constructor just for the Swagger example schema.
In my model:
/// <summary>
/// Supply a custom constructor for Swagger where you can apply defaults to control the example schema.
/// The constructor must have one parameter of type System.Reflection.ParameterInfo[].
/// Note: Setting a property to null will prevent it from showing in the Swagger example.
/// </summary>System.Reflection.ParameterInfo[].
/// </summary>
public class SwaggerConstructor : Attribute { }
In SwaggerConfig.cs:
c.SchemaFilter<ApplySchemaVendorExtensions>();
The schema extension:
public class ApplySchemaVendorExtensions : ISchemaFilter
{
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
ConstructorInfo constructor = type.GetConstructors().FirstOrDefault(c => c.GetCustomAttribute<SwaggerConstructor>() != null);
if (constructor != null)
{
schema.example = constructor.Invoke(new object[] { constructor.GetParameters() });
}
}
}
Usage:
[SwaggerConstructor]
public MyClass(System.Reflection.ParameterInfo[] decoy) : base()
{
MyProperty = false;
}
Stumbled across this just now, you can also set the tag in the XML documentation, in one of my models, I have this defined
/// <summary>
/// Note content
/// </summary>
/// <example>Any text for a note.</example>
public string Note { get; set; }
which ends up looking like this in the swagger documentation when selecting "Try It Now"
Hope that helps someone!
Using .NET 5 with Swashbuckle.AspNetCore 5.6.3, the only way I could get this to work efficiently is this:
public class ExampleDocFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
string ToCamelCase(string name) => char.ToLowerInvariant(name[0]) + name.Substring(1);
if (schema.Properties == null) return;
var setProperties = context.Type.GetProperties().ToList().Where(f => f.GetCustomAttribute<DefaultValueAttribute>() != null).Where(f => schema.Properties.Any(n => n.Key.Equals(ToCamelCase(f.Name)))).ToDictionary(f => f, f => f.GetCustomAttribute<DefaultValueAttribute>());
foreach (var prop in setProperties) schema.Properties[ToCamelCase(prop.Key.Name)].Example = OpenApiAnyFactory.CreateFor(schema.Properties[ToCamelCase(prop.Key.Name)], prop.Value.Value);
}
}
To use this - in your startup.cs:
services.AddSwaggerGen(swagger => {
...
swagger.SchemaFilter<ExampleDocFilter>();
});
I have an AJAX-enabled WCF service (with enableWebScript in the behavior) that has a ValidationFault which I created.
Here's the service:
[ServiceContract]
public interface ICoreWCF
{
/// <summary>
/// Saves the Customer.
/// </summary>
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest)]
[FaultContract(typeof(ValidationFault))]
void Customer_Save(Customer customer);
}
Here's the fault:
[DataContract]
public class ValidationFault
{
[DataMember(Name = "success")]
public bool Success { get; set; }
[DataMember(Name = "msg")]
public string ValidationMessage { get; set; }
[DataMember(Name = "errors")]
public Dictionary<string, string> Errors { get; set; }
}
I would like to send this fault back to the client javascript.
The problem is that my custom fault's DataMembers are ignored and a general exception is returned.
How can I send the errors collection to the client?
I already tried writing my own IErrorHandler similar to this, such that it uses Exception Handling Application Block to convert an exception to a fault, and then the IErrorHandler serializes the resulting fault. But it appears that the JsonErrorHandler of the WebScriptingEnablingBehavior is not dealing well with the resulting Message object.
Thanks.
If you have implemented IErrorHandler and associated it to service using using custom behavior inherited from WebHttpBehavior as sighted by link then perhaps you should try adding default request/response format etc. For example,
private class CustomWebScriptBehavior : WebHttpBehavior
{
protected override void AddServerErrorHandlers(ServiceEndpoint endpoint,
System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
// clear current error handlers
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();
// add our error handler
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(
new ErrorHandler(true));
}
private WebMessageFormat _requestFormat;
private WebMessageFormat _responseFormat;
public CustomWebScriptBehavior()
{
_requestFormat = _responseFormat = WebMessageFormat.Json;
}
public override bool AutomaticFormatSelectionEnabled
{
get { return false; }
set { throw new NotSupportedException(); }
}
public override WebMessageBodyStyle DefaultBodyStyle
{
get { return WebMessageBodyStyle.WrappedRequest; }
set { throw new NotSupportedException(); }
}
public override WebMessageFormat DefaultOutgoingRequestFormat
{
get { return _requestFormat; }
set { _requestFormat = value; }
}
public override WebMessageFormat DefaultOutgoingResponseFormat
{
get { return _responseFormat; }
set { _responseFormat = value; }
}
}
This will eliminate the need to specify WebInvoke attribute for each method.
in webinvoke you can add RequestFormat=WebMessageFormat.Json, ResponseFormat=WebMessageFormat.Json
try it