Using .NET Core 2.0 WebApi.
I have a webapi which has many endpoints where each endpoint is handling and throwing BadRequest when it fails. As below:
if(data == null)
{
return BadRequest("Data must not be blank.");
}
Now since these status codes are repetitive in my api, I was thinking to create a Helper method which would return BadRequest back to my API.
So I created a static helper class. But the issue here is BadRequest is part of ControllerBase and is not available in my helper class. What is the best way to create this method that would return a BadRequest.
--Updated---
I want something like this:
public static BadRequest GetBadRequestMessage(string message)
{
return BadRequest(message);
}
I have also tried as:
public static BadRequestResult GetBadRequestMessage(string message)
{
return new BadRequestResult(message);
}
But this gives error: Severity Code Description Project File Line Suppression State
Error CS1729 'BadRequestResult' does not contain a constructor that takes 1 arguments
You can manually initialize the action result
return new BadRequestObjectResult("error message here");
Which is basically what the ControllerBase does internally
/// <summary>
/// Creates an <see cref="BadRequestResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response.
/// </summary>
/// <returns>The created <see cref="BadRequestResult"/> for the response.</returns>
[NonAction]
public virtual BadRequestResult BadRequest()
=> new BadRequestResult();
/// <summary>
/// Creates an <see cref="BadRequestObjectResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response.
/// </summary>
/// <param name="error">An error object to be returned to the client.</param>
/// <returns>The created <see cref="BadRequestObjectResult"/> for the response.</returns>
[NonAction]
public virtual BadRequestObjectResult BadRequest(object error)
=> new BadRequestObjectResult(error);
/// <summary>
/// Creates an <see cref="BadRequestObjectResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response.
/// </summary>
/// <param name="modelState">The <see cref="ModelStateDictionary" /> containing errors to be returned to the client.</param>
/// <returns>The created <see cref="BadRequestObjectResult"/> for the response.</returns>
[NonAction]
public virtual BadRequestObjectResult BadRequest(ModelStateDictionary modelState)
{
if (modelState == null)
{
throw new ArgumentNullException(nameof(modelState));
}
return new BadRequestObjectResult(modelState);
}
Source
In .net core 5 project create BadRequestConfig class in your ioc folder/class library :
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using System.Linq;
namespace Ioc
{
public static class BadRequestConfig
{
// error 400 handling - remove extra fields in error model - remove if(ModelState.IsValid)
public static IMvcBuilder AddBadRequestServices(this IMvcBuilder services)
{
services.ConfigureApiBehaviorOptions(options =>
options.InvalidModelStateResponseFactory = actionContext =>
{
var modelState = actionContext.ModelState.Values;
var allErrors = actionContext.ModelState.Values.SelectMany(v => v.Errors);
return new BadRequestObjectResult(new
{
StatusCode = 400,
Message = string.Join(" - ", allErrors.Select(e => e.ErrorMessage))
});
});
return services;
}
}
}
Then add AddBadRequestServices() method in ConfigureServices method in your startup.cs file :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbServices();
services.AddAppServices();
services.AddControllers().AddBadRequestServices(); // here
services.AddJwtAuthentication();
services.AddSwaggerServices();
}
By this solution it is not necessary to write if(ModelState.IsValid) in your actions.
Related
I have a simple .NET 6 API that is using Swashbuckle (6.3.1).
I've noticed that when I do a request from Swagger UI (after hitting F5 in VS2022) and when I return error details they are not displayed in Swagger UI, but when the same request is done in a new chrome tab the error details are returned, same with Postman.
when I do the same request in another tab I get this (correct) response:
I noticed that Swagger UI is doing a fetch type request, but when I do a "normal" request I get a document type request:
Swagger UI:
Standard request:
My question is how can I configure Swashbuckle so that I get error details when I do a request from Swagger UI.
As requested I'm adding my controller:
[ApiController]
public class LocalizationController : ApiController
{
/// <summary>
/// Get supported languages
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpGet("languages")]
[AllowAnonymous]
[Cache(CacheDuration.FiveMinutes, 1, CacheTags.Languages)]
public async Task<IEnumerable<LanguageDto>> GetLanguages([FromRoute] GetLanguages model) => await Process(model);
/// <summary>
/// Get localized strings in requested language
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpGet("{language}")]
[ProducesResponseType(typeof(IEnumerable<LocalizationDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[AllowAnonymous]
[Cache(CacheDuration.FiveMinutes, 1, CacheTags.Languages)]
public async Task<IEnumerable<LocalizationDto>> GetLocalizations([FromRoute] GetLocalizations model) => await Process(model);
}
and my base ApiController:
public abstract class ApiController : ControllerBase
{
/// <summary>
/// MediatR mediator
/// </summary>
protected IMediator Mediator => HttpContext.RequestServices.GetRequiredService<IMediator>();
/// <summary>
/// Maps the given object to the given type
/// </summary>
protected IMapper Mapper => HttpContext.RequestServices.GetRequiredService<IMapper>();
protected async Task<TResponse> Process<TResponse>(ICommand<TResponse> command)
{
return await Mediator.Send(command);
}
/// <summary>
/// Process command - Create, Update, Delete
/// </summary>
/// <param name="command"></param>
/// <typeparam name="TModel"></typeparam>
/// <typeparam name="TResponse"></typeparam>
/// <returns></returns>
protected async Task<TResponse> Process<TModel, TResponse>(ICommand<TModel> command)
{
var result = await Mediator.Send(command);
return Mapper.Map<TResponse>(result);
}
/// <summary>
/// Process query - only Read
/// </summary>
/// <param name="query"></param>
/// <typeparam name="TResponse"></typeparam>
/// <returns></returns>
protected async Task<TResponse> Process<TResponse>(IQuery<TResponse> query)
{
return await Mediator.Send(query);
}
}
Per documentation it seems like it's only possible to add either single routes, one by one, or add all routes in annotated (attribute routing) controllers
DOCS: Routing to controller actions in ASP.NET Core
Is it possible to add only all routes belonging to single Controller?
Using UseEndpoints(e => e.MapControllers()) will add all controllers that are annotated, using UseEndpoints(e => e.MapControllerRoute(...)) seems to be able to add only single controller/action route, not all routes that are annotated in given controller
Sample controller:
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("[controller]")]
public class MyApiController
{
[Route("/")]
[Route("[action]")]
[HttpGet]
public ResponseType Index()
{
// ...
}
[Route("[action]")]
public ResponseType GetListing()
{
// ...
}
}
One solution I found is to build a custom MVC feature provider and implement an extension method that allows you to specify exactly which controllers you want registered.
public static class MvcExtensions
{
/// <summary>
/// Finds the appropriate controllers
/// </summary>
/// <param name="partManager">The manager for the parts</param>
/// <param name="controllerTypes">The controller types that are allowed. </param>
public static void UseSpecificControllers(this ApplicationPartManager partManager, params Type[] controllerTypes)
{
partManager.FeatureProviders.Add(new InternalControllerFeatureProvider());
partManager.ApplicationParts.Clear();
partManager.ApplicationParts.Add(new SelectedControllersApplicationParts(controllerTypes));
}
/// <summary>
/// Only allow selected controllers
/// </summary>
/// <param name="mvcCoreBuilder">The builder that configures mvc core</param>
/// <param name="controllerTypes">The controller types that are allowed. </param>
public static IMvcCoreBuilder UseSpecificControllers(this IMvcCoreBuilder mvcCoreBuilder, params Type[] controllerTypes) => mvcCoreBuilder.ConfigureApplicationPartManager(partManager => partManager.UseSpecificControllers(controllerTypes));
/// <summary>
/// Only instantiates selected controllers, not all of them. Prevents application scanning for controllers.
/// </summary>
private class SelectedControllersApplicationParts : ApplicationPart, IApplicationPartTypeProvider
{
public SelectedControllersApplicationParts()
{
Name = "Only allow selected controllers";
}
public SelectedControllersApplicationParts(Type[] types)
{
Types = types.Select(x => x.GetTypeInfo()).ToArray();
}
public override string Name { get; }
public IEnumerable<TypeInfo> Types { get; }
}
/// <summary>
/// Ensure that internal controllers are also allowed. The default ControllerFeatureProvider hides internal controllers, but this one allows it.
/// </summary>
private class InternalControllerFeatureProvider : ControllerFeatureProvider
{
private const string ControllerTypeNameSuffix = "Controller";
/// <summary>
/// Determines if a given <paramref name="typeInfo"/> is a controller. The default ControllerFeatureProvider hides internal controllers, but this one allows it.
/// </summary>
/// <param name="typeInfo">The <see cref="TypeInfo"/> candidate.</param>
/// <returns><code>true</code> if the type is a controller; otherwise <code>false</code>.</returns>
protected override bool IsController(TypeInfo typeInfo)
{
if (!typeInfo.IsClass)
{
return false;
}
if (typeInfo.IsAbstract)
{
return false;
}
if (typeInfo.ContainsGenericParameters)
{
return false;
}
if (typeInfo.IsDefined(typeof(Microsoft.AspNetCore.Mvc.NonControllerAttribute)))
{
return false;
}
if (!typeInfo.Name.EndsWith(ControllerTypeNameSuffix, StringComparison.OrdinalIgnoreCase) &&
!typeInfo.IsDefined(typeof(Microsoft.AspNetCore.Mvc.ControllerAttribute)))
{
return false;
}
return true;
}
}
}
Put the extensions class wherever in your project, and use like this
public void ConfigureServices(IServiceCollection services)
{
// put this line before services.AddControllers()
services.AddMvcCore().UseSpecificControllers(typeof(MyApiController), typeof(MyOtherController));
}
Source: https://gist.github.com/damianh/5d69be0e3004024f03b6cc876d7b0bd3
Courtesy of Damian Hickey.
I've tried Dependency injection, but that always gives me a HttpContextAccessor.Current or ActionContext as null because I'm not in a request state (I think). So how can I get this context to just take a view, transform it to a html string (with Model if necessary) and throw it back in JS ? I even tried to call directly the Controller action, but it always gives HttpContext as null... I'm using Asp.NET Core 3.
Please, if someone has been going through, help me :-)
Thanks,
Edit:
I have an asp.net core based on Electron.net for a desktop application. I use a lot of IPC communication to retrieve data from backend c# using Electron.IpcMain.On. I register an action as listener in c# in a class. The main problem is that this class is really outside a normal HttpRequest or a Controller. Here is some sample code:
IpcBase Class
public abstract class IpcBase: IBaseIpcCommunicationClass
{
/// <summary>
/// Find a way to get rid of this and take more than the first window
/// </summary>
protected static BrowserWindow _mainWindow = Electron.WindowManager.BrowserWindows.First();
/// <summary>
/// Send an ipcEvent with a parameter class
/// </summary>
/// <param name="parameters">Parameters to fill</param>
public void RegisterIpcEvent<T>(IpcRegisterModel<T> registerModel) => Electron.IpcMain.On(registerModel.key, o => registerModel.action((T)
((JObject)o).ToObject(typeof(T))));
/// <summary>
/// Send a reply inside a registerevent
/// </summary>
/// <typeparam name="T">Type of model</typeparam>
/// <param name="model">model</param>
public void SendReply<T>(IpcSendParamsModel<T> model)
{
if (!string.IsNullOrEmpty(model.replyTo))
{
Electron.IpcMain.Send(_mainWindow, model.replyTo, model);
}
}
...
}
IpcUI (to get the controller view, just like an ajax call on a controller that retrieve the view in String (I already have that, but not with Ipc)
public class IpcUI: IpcBase
{
public IpcUI(IRazorViewToStringService razorViewRenderService)
{
Console.WriteLine("IpcUI::Constructor");
RegisterIpcEvent(new IpcRegisterModel<IpcSendParamsModel<AjaxPartialModel>>("renderPartial", async (param) =>
{
var param = new IpcSendParamsModel<AjaxPartialModel>("RenderPartial")
{
key = "renderPartial",
model = new AjaxPartialModel()
{
DataModel = "{items: [{\r\n MaterialIcon: \"\",\r\n Title: \"Games\",\r\n Selectable: true,\r\n Active: true,\r\n Key: \"GAMES\",\r\n BadgeCaption: \"new\",\r\n BadgeValue: \"123\",\r\n BadgeColor: \"red darken-1\",\r\n BadgePartialLink: \"\",\r\n BadgeContainerLink: \"\",\r\n BadgeModelLink: \"\",\r\n PartialLink: \"Home/Index\",\r\n ContainerLink: \"#body-content\",\r\n ModelLink: \"\"\r\n }] }".JsonDeserialize<MenuModelHeader>(),
PartialName = "PartialViews/_TopMenu"
}
};
try
{
param.results =
await razorViewRenderService.CreateAndResolveInstanceFromGeneric().RenderViewToStringAsync($"~/Views/{param.model.PartialName}.cshtml",
param.model.DataModel);
}
catch (Exception e)
{
IpcClasses.ExceptionManager.SendException(this, e, $"IpcUI params: {param.model.JsonSerialize()}");
}
}));
}
}
Razor Service (Mostly taken from here Generate string from view)
Added in startup:
services.AddHttpContextAccessor();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IRazorViewToStringService, RazorRazorViewToStringService>();
When I create an instance of IpcUI, DI gives me the service, but without any HttpContext or ActionContext... Sorry for the lack of information from my last edit :-). Hope it is a bit more specific.
Oh ! I forgot something, IpcUI is created at runtime not with a new (because that don't work) but with a custom extension function that retrieves the IServiceProvider for DI:
In startup
ExtensionsUtils.ServiceProvider = app.ApplicationServices;
In ExtensionsUtils
/// <summary>
/// This is called in configure services to get the collection of services in an extension static class
/// </summary>
public static IServiceProvider ServiceProvider { get; set; }
/// <summary>
/// Create a reference from type T with DI
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="o"></param>
/// <returns></returns>
public static T CreateAndResolveInstanceFromGeneric<T>(this T o)
{
return (T)ActivatorUtilities.CreateInstance<T>(ServiceProvider);
}
Edit 2:
I have tried to access IRazorViewToStringService from a real controller constructor and it's null again... What Am I doing wrong ???
private readonly IRazorViewToStringService _razorViewRenderService;
public BaseController(IRazorViewToStringService razorViewRenderService)
{
_razorViewRenderService = razorViewRenderService;
}
...
/// <summary>
/// Return a success http code and a View rendered as string
/// </summary>
/// <param name="ViewName">Name of MVC View (or PartialView)</param>
/// <param name="Model">Model to pass if any</param>
/// <returns>JSON: { result: "type", description: "Html Code" }</returns>
public async Task<ActionResult> CheckAndReturnView(string ViewName, object Model = null)
{
return Ok(await _razorViewRenderService.RenderViewToStringAsync(ViewName, Model));
}
I'm using Web API 2 with Ninject and i'm getting the following error when i've got multiple parallel HTTP calls.
A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.
I didn't get this error before, all the code that retrieves the data from the database is async so i'm thinking its a Ninject scope issue.
NinjectWebCommon
kernel.BindHttpFilter<MyAuthenticateFilter>(FilterScope.Action);
// Database context
kernel.Bind<IUnitOfWorkAsyncFactory>().To<UnitOfWorkAsyncFactory>().InRequestScope();
kernel.Bind<IUnitOfWorkAsync>().To<UnitOfWork>().InRequestScope();
kernel.Bind<IDataFactory>().To<DataFactory>().InRequestScope();
All the Application and domain handlers are also async and make use of the same DataFactory and UnitOfWorkAsyncFactory.
Seems like there's a threading issue with the IAuthenticationFilter.
The factory pattern used for the DbContext.
public class DataFactory : Disposable, IDataFactory
{
private MyContext DbContext { get; set; }
public IDataContextAsync GetDataContext()
{
return DbContext ?? (DbContext = new MyContext());
}
public void AfterDispose()
{
DbContext = null;
}
public DataFactory()
{
}
public DataFactory(MyContext context)
{
DbContext = context;
}
}
I remember some time ago having issues with filter injection in WebApi and scopes being lost.
As far as I remember using this implementation of IDependencyResolver (can not remember where I got it) made the issue go away.
using System.Web.Http.Dependencies;
using global::Ninject.Syntax;
public class DependencyResolverWebApiNinject : DependencyScopeWebApiNinject, IDependencyResolver
{
public DependencyResolverWebApiNinject(IResolutionRoot resolutionRoot)
: base(resolutionRoot)
{
}
public virtual IDependencyScope BeginScope()
{
return this;
}
}
Where DependencyScopeWebApiNinject is :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http.Dependencies;
using global::Ninject;
using global::Ninject.Infrastructure.Disposal;
using global::Ninject.Parameters;
using global::Ninject.Syntax;
public class DependencyScopeWebApiNinject : DisposableObject, IDependencyScope
{
/// <summary>
/// Initializes a new instance of the <see cref="DependencyScopeWebApiNinject"/> class.
/// </summary>
/// <param name="resolutionRoot">The resolution root.</param>
public DependencyScopeWebApiNinject(IResolutionRoot resolutionRoot)
{
this.ResolutionRoot = resolutionRoot;
}
/// <summary>
/// Gets the resolution root.
/// </summary>
/// <value>The resolution root.</value>
protected IResolutionRoot ResolutionRoot
{
get;
private set;
}
/// <summary>
/// Gets the service of the specified type.
/// </summary>
/// <param name="serviceType">The type of the service.</param>
/// <returns>The service instance or <see langword="null"/> if none is configured.</returns>
public object GetService(Type serviceType)
{
var request = this.ResolutionRoot.CreateRequest(serviceType, null, new Parameter[0], true, true);
return this.ResolutionRoot.Resolve(request).SingleOrDefault();
}
/// <summary>
/// Gets the services of the specifies type.
/// </summary>
/// <param name="serviceType">The type of the service.</param>
/// <returns>All service instances or an empty enumerable if none is configured.</returns>
public IEnumerable<object> GetServices(Type serviceType)
{
return this.ResolutionRoot.GetAll(serviceType).ToList();
}
}
hope this will help
I've been able create whatever endpoints I've wanted as long as the parameters for each one is different:
public IHttpActionResult GetFightersByWeightClass(string WeightClass)
...
public IHttpActionResult GetFighterByExactName(string NameEquals)
...
But as soon as I try to create two differently named functions that share the same parameters I am unable to use both. I have two endpoints that don't require parameters, shown below:
public class FighterController : ApiController
{
/// <summary>
/// Gets all fighters.
/// </summary>
/// <returns></returns>
[ActionName("GetAllFighters")]
public IEnumerable<Fighter> GetAllFighters()
{
return allFighters;
}
/// <summary>
/// Gets all fighters that are currently undefeated.
/// </summary>
/// <returns></returns>
[ActionName("GetAllUndefeatedFighters")]
public IHttpActionResult GetAllUndefeatedFighters()
{
var results = allFighters.FindAll(f => f.MMARecord.Losses == 0);
if (results == null)
{
return NotFound();
}
return Ok(results);
}
}
Both URLs return this:
{"Message":"An error has occurred.","ExceptionMessage":"Multiple actions were found that match the request: \r\nGetAllFighters on type MMAAPI.Controllers.FighterController\r\nGetAllUndefeatedFighters on type MMAAPI.Controllers.FighterController","ExceptionType":"System.InvalidOperationException","StackTrace":" at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.SelectAction(HttpControllerContext controllerContext)\r\n at System.Web.Http.Controllers.ApiControllerActionSelector.SelectAction(HttpControllerContext controllerContext)\r\n at System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"}
Not sure why this is happening they each have their own unique action and function name, so I thought they would work like this...:
http://localhost:55865/api/fighter/GetAllUndefeatedFighters -- Just shows fighters with zero losses
http://localhost:55865/api/fighter/ -- shows all fighters
...but instead neither works. If I remove one of them, they other works and vice versa. So they aren't working when they are both active. Any idea why?
Web API allows you to use Attribute routing to customize endpoint URIs.
To use it, add:
config.MapHttpAttributeRoutes();
to the Register method in your WebApiConfig class. Then you can set the endpoints to whatever you want regardless of the Action name.
[Route("getallfighters"), HttpGet, ResponseType(typeof(Fighter))]
public IHttpActionResult ThisNameDoesntMatterNow()
{
//...
}
And your URI becomes:
api/fighter/getallfighters
You can even add attribute routing to your controller:
[RoutePrefix("api/v1/fighters")]
public class FightersController : ApiController
{
//...
}
A combination of the two other answers works well for me. (I've changed the names slightly from the question.)
[RoutePrefix("api/v1/fighters")]
public class FighterController : ApiController
{
/// <summary>
/// Gets all fighters.
/// </summary>
/// <returns>An enumeration of fighters.</returns>
[Route(""), HttpGet]
public IEnumerable<Fighter> GetAllFighters()
{
return allFighters;
}
/// <summary>
/// Gets all fighters that are currently undefeated.
/// </summary>
/// <returns>An enumeration of fighters.</returns>
[Route("undefeated"), HttpGet]
public IEnumerable<Fighter> GetAllUndefeatedFighters()
{
return allFighters.FindAll(f => f.MMARecord.Losses == 0);
}
}
As such, your endpoints would be:
GET /api/v1/fighters
GET /api/v1/fighters/undefeated
Use route attribute
/// <summary>
/// Gets all fighters.
/// </summary>
/// <returns></returns>
[HttpGet]
[System.Web.Http.Route("api/GetAllFighters")]
public IEnumerable<Fighter> GetAllFighters()
{
return allFighters;
}
/// <summary>
/// Gets all fighters that are currently undefeated.
/// </summary>
/// <returns></returns>
[HttpGet]
[System.Web.Http.Route("api/GetAllUndefeatedFighters")]
public IHttpActionResult GetAllUndefeatedFighters()
{
var results = allFighters.FindAll(f => f.MMARecord.Losses == 0);
if (results == null)
{
return NotFound();
}
return Ok(results);
}
and call two method using different route
http://www.yourdomain/api/GetAllFighters
http://www.yourdomain/api/GetAllUndefeatedFighters