I want to display a response in Swagger UI in XML format instead of JSON. How I can achieve this? This is the code I want to adapt:
[SwaggerResponse((int)HttpStatusCode.OK, Type = typeof(FeedModel))]
public async Task<IActionResult> GetCompanyPostFeed(Guid companyId)
{
var feed = new FeedModel();
// Some database requests
return Content(feed, "text/xml", Encoding.UTF8);
}
You could try decorating the method with an attribute SwaggerProducesAttribute as described here:
[SwaggerProduces("text/xml")]
[SwaggerResponse((int)HttpStatusCode.OK, Type = typeof(FeedModel))]
public async Task<IActionResult> GetCompanyPostFeed(Guid companyId)
In keeping with the dislike of link-only answers, I'll reproduce some of the relevant bits of that article here:
[AttributeUsage(AttributeTargets.Method)]
public class SwaggerProducesAttribute : Attribute
{
public SwaggerProducesAttribute(params string[] contentTypes)
{
this.ContentTypes = contentTypes;
}
public IEnumerable<string> ContentTypes { get; }
}
public class ProducesOperationFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
var attribute = apiDescription.GetControllerAndActionAttributes<SwaggerProducesAttribute>().SingleOrDefault();
if (attribute == null)
{
return;
}
operation.produces.Clear();
operation.produces = attribute.ContentTypes.ToList();
}
}
Then in SwaggerConfig.cs, you'll need something like:
config
.EnableSwagger(c =>
{
...
c.OperationFilter<ProducesOperationFilter>();
...
}
Related
I have tried the solution here: How to properly set up snake case JSON for dotnet core api? but seems to be this is only for request/response payloads. Is there a way to force snake_case format on my [FromQuery] parameters?
Currently this is what I have as of the moment
What you did is about json serialization in asp.net core but what you want is to change the Swagger UI. They are the quite different things.
The first way, you can add attribute like: [FromQuery(Name = "account_number")].
Or you can custom an attribute to avoid writing snake_case:
public class CustomFromQueryAttribute : FromQueryAttribute
{
public CustomFromQueryAttribute(string name)
{
Name = name.ToSnakeCase();
}
}
Custom ToSnakeCase for string extension:
public static class StringExtensions
{
public static string ToSnakeCase(this string o) =>
Regex.Replace(o, #"(\w)([A-Z])", "$1_$2").ToLower();
}
Usage:
public voidGet([CustomFromQuery("AccountNumber")]string AccountNumber)
Note:
Actually this way equals to using public voidGet(string account_number), so the Swagger UI changed.
The second way, you can custom IOperationFilter like below to change the Swagger UI:
public class SnakecasingParameOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Parameters == null) operation.Parameters = new List<OpenApiParameter>();
else {
foreach(var item in operation.Parameters)
{
item.Name = item.Name.ToSnakeCase();
}
}
}
}
Register the service like below:
services.AddSwaggerGen(c =>
{
c.OperationFilter<SnakecasingParameOperationFilter>();
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApi5_0", Version = "v1" });
});
But this way change the request query string to snake case which has an extra _, It does not match the backend parameter. So you also need to custom value provider that looks for snake cased query parameters:
SnakeCaseQueryValueProvider:
public class SnakeCaseQueryValueProvider : QueryStringValueProvider, IValueProvider
{
public SnakeCaseQueryValueProvider(
BindingSource bindingSource,
IQueryCollection values,
CultureInfo culture)
: base(bindingSource, values, culture)
{
}
public override bool ContainsPrefix(string prefix)
{
return base.ContainsPrefix(prefix.ToSnakeCase());
}
public override ValueProviderResult GetValue(string key)
{
return base.GetValue(key.ToSnakeCase());
}
}
We also have to implement a factory for our value provider:
public class SnakeCaseQueryValueProviderFactory : IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var valueProvider = new SnakeCaseQueryValueProvider(
BindingSource.Query,
context.ActionContext.HttpContext.Request.Query,
CultureInfo.CurrentCulture);
context.ValueProviders.Add(valueProvider);
return Task.CompletedTask;
}
}
Register the services:
services.AddControllers(options =>
{
options.ValueProviderFactories.Add(new SnakeCaseQueryValueProviderFactory()); //add this...
}).AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy()
};
});
I am trying to do custom model binding in Core(2.2/3.1) with an inherited sub class.
I use IModelBinderProvider and IModelBinder to manipulate my model binding as MVC doesn't know whether to translate the base class Device to a Teddybear or a Legobrick.
IModelBinder's BindModelAsync method gets called for my Product class and I guess that is where I should look for the Data property and check its Kind. Then from the parameter bindingContext.Model extrude the Device data and replace the Data property's value with a Teddybear or Legobrick.
But bindingContext.Model is null; I have no data.
There is an example at towards the bottom of MSDN but in it, it is the root that is the base class.
I have a regular root but a property is a base/inherited class construct.
Somewhere I don't get the calls correctly hooked up or I haven't found the correct way to read data.
I guess my IModelBinderProvider is correct, it catches the Product type and adds binders to the sub classes Teddybear and Legobrick.
public class DeviceTypeDataContractProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
if (context.Metadata.ModelType == typeof(Product))
{
foreach (var type in new[] { typeof(Teddybear), typeof(Legobrick) })
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
}
}
else
{
return null;
}
return new DeviceModelBinder(binders);
}
}
The code at IModelBinder/BindModelAsync still eludes me.
public class DeviceModelBinder : IModelBinder
{
private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;
public DeviceModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
{
this.binders = binders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext){
... I totally lost it here and am beginning to feel dizzy.
}
}
Over internet comes a call like:
"product": {
"id": "56-1",
"data": {
"kind": "teddy",
"name": "Tutu"
}
}
or
"product": {
"id": "66-1",
"data": {
"kind": "lego",
"studCount": 8
}
}
which Aspnet uses to populate:
public class Product{
string Id {get;set;}
Device Data{get;set;}
}
public class Device{
string Kind {get;set}
}
public class Teddybear: Device{
string Name {get;set}
}
public class Legobrick: Device{
int StudCount {get;set}
}
The controller is regular and the custom modelling is hooked up:
[HttpPost]
public async Task<IActionResult> Create([FromBody] Product product){...
services.AddMvc(options => {
...
options.Filters.Add(new AuthorizeFilter(policy));
})
.AddJsonOptions(options => {
options.ModelBinderProviders.Insert(0, new DeviceTypeDataContractProvider());
});
I think the solution provided in documentation won't work in your case because of you using json. A simple working example would be
public class DeviceTypeDataContractProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(Product))
{
return new DeviceModelBinder();
}
return null;
}
}
public class DeviceModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var reader = new JsonTextReader(new StreamReader(bindingContext.HttpContext.Request.Body));
//loading request json
var jObject = JObject.Load(reader);
JToken data = jObject["data"];
Product result = jObject.ToObject<Product>();
switch (result.Data.Kind)
{
case "teddy":
result.Data = data.ToObject<Teddybear>();
break;
case "lego":
result.Data = data.ToObject<Legobrick>();
break;
default:
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
}
I am trying to receive the below key value pair as the input parameter to my Web API
json=%7B%0A%22MouseSampleBarcode%22%20%3A%20%22MOS81%22%0A%7D%0A
where the right of the string is the URL encoded JSON which looks like
{
"MouseSampleBarcode" : "MOS81"
}
How can I parse this and store them in to the Model class
[HttpPost]
public async Task<IHttpActionResult> Get([FromBody] CoreBarCodeDTO.RootObject coreBarCode)
{
string Bar_Code = coreBarCode.MouseSampleBarcode.ToString();
where the CoreBarCodeDTO looks like below
public class CoreBarCodeDTO
{
public class RootObject
{
public string MouseSampleBarcode { get; set; }
}
}
You could do it this way. Change your class to this definition. In your controller coreBarCode.json will have the the json which you can then work with as needed:
public class CoreBarCodeDTO
{
private string _json;
public string json { get { return _json; }
set {
string decoded = HttpUtility.UrlDecode(value);
_json = decoded;
}
}
}
Update
[HttpPost]
public async Task<IHttpActionResult> Get([FromBody] CoreBarCodeDTOcoreBarCode coreBarCode)
{
string Bar_Code = coreBarCode.json;
//work with the JSON here, with Newtonsoft for example
var obj = JObject.Parse(Bar_Code);
// obj["MouseSampleBarcode"] now = "MOS81"
}
As #Lokki mentioned in his comment. The GET verb does not have a body, you need to change that to POST or PUT (depending if you are creating/searching or updating), so your code would look like this:
[HttpPost("/")]
public async Task<IHttpActionResult> Get([FromBody] CoreBarCodeDTO.RootObject coreBarCode)
{
string Bar_Code = coreBarCode.MouseSampleBarcode.ToString();
So, as I said: Get doesn't have body.
Follow #KinSlayerUY answer.
[HttpPost("/")]
public async Task<IHttpActionResult> Post([FromBody] CoreBarCodeDTO.RootObject coreBarCode)
{
string Bar_Code = coreBarCode.MouseSampleBarcode.ToString();
...
}
If you need use GET remove [FromBody] attribute and send data as single parameters
[HttpGet("/")]
public async Task<IHttpActionResult> Get(string mouseSampleBarcode)
{
var rootObject = new CoreBarCodeDTO.RootObject
{
MouseSampleBarcode = mouseSampleBarcode
}
...
}
I have a web api, where the global configuration is configured to use:
XmlMediaTypeFormatter
My problem is I wont to extend this web api with a new controller, that uses the JsonMediaTypeFormatter instead.
Is it possible to change the MediaTypeFormatter to JSON for only one API Controller class?
My problem is not returning JSON, I have accumplished this with returning HttpResponseMessage:
return new HttpResponseMessage
{
Content = new ObjectContent<string>("Hello world", new JsonMediaTypeFormatter()),
StatusCode = HttpStatusCode.OK
};
It's on the request I get the problem. If I have an object with two properties:
public class VMRegistrant
{
public int MerchantId { get; set; }
public string Email { get; set; }
}
And my controller action takes the VMRegistrant as argument:
public HttpResponseMessage CreateRegistrant(VMRegistrant registrant)
{
// Save registrant in db...
}
But the problem is when I call the action with JSON it fails.
You can have your controller return an IHttpActionResult and use the extension method HttpRequestMessageExtensions.CreateResponse<T> and specify the formatter you want to use:
public IHttpActionResult Foo()
{
var bar = new Bar { Message = "Hello" };
return Request.CreateResponse(HttpStatusCode.OK, bar, new MediaTypeHeaderValue("application/json"));
}
Another possibility is to use the ApiController.Content method:
public IHttpActionResult Foo()
{
var bar = new Bar { Message = "Hello" };
return Content(HttpStatusCode.OK, bar, new JsonMediaTypeFormatter(), new MediaTypeHeaderValue("application/json"));
}
Edit:
One possibility is to read and deserialize the content yourself from the Request object via reading from the stream and using a JSON parser such as Json.NET to create the object from JSON:
public async Task<IHttpActionResult> FooAsync()
{
var json = await Request.Content.ReadAsStringAsync();
var content = JsonConvert.DeserializeObject<VMRegistrant>(json);
}
Yes, it's possible to change the MediaTypeFormatters for only one class/controller. If you want to save and restore the default formatters you can follow these steps:
In the beginning of the request save old formatters
Clear the formatters collection
Add the desired formatter
At the end of the request copy back the old formatters
I think this is easily done by an ActionFilterAttribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class ChangeFormatterAttribute : ActionFilterAttribute
{
private IEnumerable<MediaTypeFormatter> oldFormatters;
private MediaTypeFormatter desiredFormatter;
public ChangeFormatterAttribute(Type formatterType)
{
this.desiredFormatter = Activator.CreateInstance(formatterType) as MediaTypeFormatter;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
var formatters = actionContext.ControllerContext.Configuration.Formatters;
oldFormatters = formatters.ToList();
formatters.Clear();
formatters.Add(desiredFormatter);
base.OnActionExecuting(actionContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var formatters = actionExecutedContext.ActionContext.ControllerContext.Configuration.Formatters;
formatters.Clear();
formatters.AddRange(oldFormatters);
base.OnActionExecuted(actionExecutedContext);
}
}
And the usage:
[ChangeFormatterAttribute(typeof(JsonMediaTypeFormatter))]
public class HomeController : ApiController
{
public string Get()
{
return "ok";
}
}
// ...
[ChangeFormatterAttribute(typeof(XmlMediaTypeFormatter))]
public class ValuesController : ApiController
{
public string Get()
{
return "ok";
}
}
Maybe you could have your media type formatter only accept the type that is handled by your controller:
public class Dog
{
public string Name { get; set; }
}
public class DogMediaTypeFormatter : JsonMediaTypeFormatter
{
public override bool CanReadType(Type type)
{
return type == typeof (Dog);
}
}
Probably not the best solution though :I
I have set up a global filter for all my controller actions in which I open and close NHibernate sessions. 95% of these action need some database access, but 5% don't. Is there any easy way to disable this global filter for those 5%. I could go the other way round and decorate only the actions that need the database, but that would be far more work.
You could write a marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute
{
}
and then in your global action filter test for the presence of this marker on the action:
public class MyGlobalActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionDescriptor.GetCustomAttributes(typeof(SkipMyGlobalActionFilterAttribute), false).Any())
{
return;
}
// here do whatever you were intending to do
}
}
and then if you want to exclude some action from the global filter simply decorate it with the marker attribute:
[SkipMyGlobalActionFilter]
public ActionResult Index()
{
return View();
}
Though, the accepted answer by Darin Dimitrov is fine and working well but, for me, the simplest and most efficient answer found here.
You just need to add a boolean property to your attribute and check against it, just before your logic begins:
public class DataAccessAttribute: ActionFilterAttribute
{
public bool Disable { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Disable) return;
// Your original logic for your 95% actions goes here.
}
}
Then at your 5% actions just use it like this:
[DataAccessAttribute(Disable=true)]
public ActionResult Index()
{
return View();
}
In AspNetCore, the accepted answer by #darin-dimitrov can be adapted to work as follows:
First, implement IFilterMetadata on the marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute, IFilterMetadata
{
}
Then search the Filters property for this attribute on the ActionExecutingContext:
public class MyGlobalActionFilter : IActionFilter
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.Filters.OfType<SkipMyGlobalActionFilterAttribute>().Any())
{
return;
}
// etc
}
}
At least nowadays, this is quite easy: to exclude all action filters from an action, just add the OverrideActionFiltersAttribute.
There are similar attributes for other filters: OverrideAuthenticationAttribute, OverrideAuthorizationAttribute and OverrideExceptionAttribute.
See also https://www.strathweb.com/2013/06/overriding-filters-in-asp-net-web-api-vnext/
Create a custom Filter Provider. Write a class which will implement IFilterProvider. This IFilterProvider interface has a method GetFilters which returns Filters which needs to be executed.
public class MyFilterProvider : IFilterProvider
{
private readonly List<Func<ControllerContext, object>> filterconditions = new List<Func<ControllerContext, object>>();
public void Add(Func<ControllerContext, object> mycondition)
{
filterconditions.Add(mycondition);
}
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
return from filtercondition in filterconditions
select filtercondition(controllerContext) into ctrlContext
where ctrlContext!= null
select new Filter(ctrlContext, FilterScope.Global);
}
}
=============================================================================
In Global.asax.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
MyFilterProvider provider = new MyFilterProvider();
provider.Add(d => d.RouteData.Values["action"].ToString() != "SkipFilterAction1 " ? new NHibernateActionFilter() : null);
FilterProviders.Providers.Add(provider);
}
protected void Application_Start()
{
RegisterGlobalFilters(GlobalFilters.Filters);
}
Well, I think I got it working for ASP.NET Core.
Here's the code:
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Prepare the audit
_parameters = context.ActionArguments;
await next();
if (IsExcluded(context))
{
return;
}
var routeData = context.RouteData;
var controllerName = (string)routeData.Values["controller"];
var actionName = (string)routeData.Values["action"];
// Log action data
var auditEntry = new AuditEntry
{
ActionName = actionName,
EntityType = controllerName,
EntityID = GetEntityId(),
PerformedAt = DateTime.Now,
PersonID = context.HttpContext.Session.GetCurrentUser()?.PersonId.ToString()
};
_auditHandler.DbContext.Audits.Add(auditEntry);
await _auditHandler.DbContext.SaveChangesAsync();
}
private bool IsExcluded(ActionContext context)
{
var controllerActionDescriptor = (Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor;
return controllerActionDescriptor.ControllerTypeInfo.IsDefined(typeof(ExcludeFromAuditing), false) ||
controllerActionDescriptor.MethodInfo.IsDefined(typeof(ExcludeFromAuditing), false);
}
The relevant code is in the 'IsExcluded' method.
You can change your filter code like this:
public class NHibernateActionFilter : ActionFilterAttribute
{
public IEnumerable<string> ActionsToSkip { get; set; }
public NHibernateActionFilter(params string[] actionsToSkip)
{
ActionsToSkip = actionsToSkip;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (null != ActionsToSkip && ActionsToSkip.Any(a =>
String.Compare(a, filterContext.ActionDescriptor.ActionName, true) == 0))
{
return;
}
//here you code
}
}
And use it:
[NHibernateActionFilter(new[] { "SkipFilterAction1 ", "Action2"})]