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
Related
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));
}
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.
In my Web api project I am having a controller DocumentViewerV1Controller which has a code as:
/// <summary>
///
/// </summary>
/// <param name="documentViewerService"></param>
public DocumentViewerV1Controller(IDocumentViewerService<HtmlViewInformation> documentViewerService)
{
_documentViewerHtmlService = documentViewerService;
}
/// <summary>
///
/// </summary>
/// <param name="documentViewerService"></param>
public DocumentViewerV1Controller(IDocumentViewerService<PageImage> documentViewerService)
{
_documentViewerImageService = documentViewerService;
}/// <summary>
/// Rendering Document as Html
/// </summary>
/// <returns></returns>
[HttpGet]
[Route(WebApiConfig.RootApiUri + "/v1/viewashtml/{docid}")]
public string ViewAsHtml(string docId)
{
var documentInfo = new DocumentInfo { DocumentName = HostingEnvironment.MapPath("~/Uploads/") + docId };
var response = _documentViewerHtmlService.RenderDocument(documentInfo, DocumentRenderType.Html);
return GenerateResponse(response);
}
When I run my service, make a call and debug the constructor initialization, It doesn't goes through the initialization which makes _documentViewerHtmlService as null, eventually fails returning Null reference Exception.
Is it possible to have service Interface as IDocumentViewerService?
Yes, but you'll need to remove one of the constructors or tell it which one to use.
container.RegisterType<IViewerInformation, HtmlViewerInformation>();
container.RegisterType<IDocumentViewerService<IViewerInformation>, DocumentViewerServce<IViewerInformation>>();
You may / may not also need to initialize some of the constructors which you can do by
container.RegisterType<IDocumentViewerService<IViewerInformation>, DocumentViewerServce<IViewerInformation>>(
new InjectionConstructor(...));
Assuming that the REST and Data access interactions will be somewhat standard, I want to be able to create an abstract type to represent entity objects to pass back and forth from REST client (e.g. JavaScript in a browser) to the persistence layer without writing/registering/routing a bunch of different WebAPI controllers.
How can one create one controller base type implementation to manage persistence of several different entity type REST interactions? I.e. how can I make this work with WebAPI?
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Web;
using System.Web.Http;
namespace DynamicEntityApiControllers
{
/// <summary>
///
/// </summary>
public class EntityController<T> : ApiController
where T : EntityObject
{
private readonly IDaoProvider daoProvider;
/// <summary>
/// Initializes a new instance of the <see cref="EntityController{T}"/> class.
/// </summary>
/// <param name="daoProvider">The DAO provider.</param>
protected EntityController(IDaoProvider daoProvider)
{
this.daoProvider = daoProvider;
}
/// <summary>
/// Gets the name of the collection.
/// </summary>
/// <value>
/// The name of the collection.
/// </value>
protected string CollectionName
{
get
{
return (string)this.RequestContext.RouteData.Values["controller"];
}
}
/// <summary>
/// Gets the specified entity by key from the collection.
/// </summary>
/// <param name="key">The key.</param>
/// <returns></returns>
public T Get(string key)
{
return this.daoProvider.Output<T>(this.CollectionName, key).Data;
}
/// <summary>
/// Creates or updates the specified entity by key in the collection.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="updating">The updating.</param>
/// <returns></returns>
public T Put(string key, [FromBody] T updating)
{
var daoObject = DaoEntity.Create(this.CollectionName, key, updating);
return this.daoProvider.Input<T>(daoObject).Data;
}
/// <summary>
/// Creates or Updates the collection specified entirely.
/// </summary>
/// <param name="replacingCollection">The replacing collection.</param>
/// <returns></returns>
public IEnumerable<T> Put([FromBody] IEnumerable<T> replacingCollection)
{
return this.daoProvider.Input<T>(replacingCollection
.Select(o => DaoEntity.Create(this.CollectionName, o)));
}
/// <summary>
/// creates a new entity in the specified collection.
/// </summary>
/// <param name="creating">The creating.</param>
/// <returns></returns>
public T Post([FromBody] T creating)
{
var daoEntity = DaoEntity.Create(this.CollectionName, creating);
return this.daoProvider.Input(daoEntity).Data;
}
/// <summary>
/// Deletes the specified entity by key from the collection.
/// </summary>
/// <param name="key">The key.</param>
public void Delete(string key)
{
this.daoProvider.Delete(this.CollectionName, key);
}
}
}
the JavaScript client must look/act something like this:
$.ajax({url: '/Book', method: 'POST', data: {title: 'This StackOverflow Post', pageCount: '124' })
.done(function(data) {
$.get('/Book/'+data.Key).done(function(data) {
// this will return the book you just created
console.dir(data);
})
})
$.ajax({url: '/Book', method: 'POST', data: {title: 'Some other StackOverflow Post', pageCount: '236' })
.done(function(data) {
$.get('/Magazine/'+data.Key).done(function(data) {
// this will return 404 status code because you're asking for the book's key
// in the Magazines collection
console.dir(data);
})
})
the sample solution has a bunch TODO here (like actually do right with ETag header, if-match, etc...) but I had to do about 4 queries across StackOverflow to find these bits.
Prerequisites:
Autofac (IoC) integration with WebAPI
WebAPI Controller Location / DependencyResolver (these are not the same things)
C# Generics
Reflection IL Emitting
very minimal REST understanding and what an ETag is.
GIT source folder
briefly, here is the logic:
create a base type (entityobject)
identify all those entities at IoC Registration time via reflection (Implementer is responsible for specifying which assemblies to scan)
create concrete implementations of abstract class EntityController via IL Emitting
Tell the SupportsDynamicControllerTypeResolver about these dynamic types
IDaoProvider implementation is the "persistence" layer. implement your persistence logic here.. in this sample, an in memory dictionary is used.
And what happens when a request is made:
- RouteTable is interrogated (finds {controller}/{key} route which is an HttpRoute
- Controller name matches EntityObject implementer name minus Entity. I.e. BookEntity => BookController
- SupportsDynamicControllerTypeResolver is required here to get dynamic assembly types as valid candidates for WebApi to try to activate.
- HTTP Put/Get/Post/Delete methods find the correct method
- persistence layer does the rest