Actions require unique method/path combination for Swagger - c#

I have 2 HTTP GET method in same controller and give me this error
HTTP method "GET" & path "api/DataStore" overloaded by actions - DPK.HostApi.Controllers.DataStoreController.GetByIdAsync (DPK.HostApi),DPK.HostApi.Controllers.DataStoreController.GetAllAsync (DPK.HostApi). Actions require unique method/path combination for Swagger 2.0.
My Controller :
[Route("api/[controller]")]
[ApiController]
public class DataStoreController : ApiControllerBase
{
private readonly IDataStoreService _dataStoreService;
public DataStoreController(IDataStoreService dataStoreService)
{
_dataStoreService = dataStoreService;
}
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody] DataStoreCommand dataStoreCommand)
{
try
{
if (ModelState.IsValid)
{
await _dataStoreService.PostAsync(dataStoreCommand);
return Ok();
}
var errorList = ModelState.Values.SelectMany(m => m.Errors).Select(e => e.ErrorMessage).ToList();
return ValidationProblem();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
[HttpPut]
public async Task<IActionResult> PutAsync([FromBody] DataStoreCommand dataStoreCommand)
{
try
{
if (ModelState.IsValid)
{
await _dataStoreService.PutAsync(dataStoreCommand);
return Ok();
}
var errorList = ModelState.Values.SelectMany(m => m.Errors).Select(e => e.ErrorMessage).ToList();
return ValidationProblem();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
[HttpDelete]
public async Task<IActionResult> DeleteAsync(int id)
{
try
{
if (ModelState.IsValid)
{
var item = await _dataStoreService.GetByIdAsync(id);
await _dataStoreService.DeleteAsync(item);
return Ok();
}
var errorList = ModelState.Values.SelectMany(m => m.Errors).Select(e => e.ErrorMessage).ToList();
return ValidationProblem();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
[HttpGet]
public async Task<DataStoreQuery> GetByIdAsync(int id)
{
try
{
return await _dataStoreService.GetByIdAsync(id);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
[HttpGet]
public async Task<IEnumerable<DataStoreQuery>> GetAllAsync(string instanceName, string dbname, string userName, string userPass, bool isActive, DateTime? startCreatedDate, DateTime? endCreatedDate, DateTime? startModifiedDate, DateTime? endModifiedDate)
{
object[] parameters = { instanceName, dbname, userName, userPass, isActive, startCreatedDate, endCreatedDate, startModifiedDate, endModifiedDate};
var parameterName = "#instanceName , #dbname , #userName , #userPass , #isActive , #startCreatedDate , #endCreatedDate , #startModifiedDate , #endModifiedDate";
try
{
return await _dataStoreService.ExecWithStoreProcedure(parameterName, parameters);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
My Startup :
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info
{
Version = "v1",
Title = " ",
Description = " ",
TermsOfService = "None",
Contact = new Contact() { Name = " ", Email = " ", Url = " " }
});
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
}

You can resolve it as follows:
services.AddSwaggerGen (c =>
{
other configs;
c.ResolveConflictingActions (apiDescriptions => apiDescriptions.First ());
});
//in the Startup.cs class in the ConfigureServices method
or you can put routes to differentiate your methods, for example:
[HttpGet("~/getsomething")]
[HttpGet("~/getothersomething")]

I changed the controller route to following:
[Route("api/[controller]/[action]")]
or you can also define explicit route for action as well:
[Route("GetById")]

you need to map id into HttpGet.
[HttpGet("{id}")]
public async Task<DataStoreQuery> GetByIdAsync(int id)
{
try
{
return await _dataStoreService.GetByIdAsync(id);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
when you specify HttpGet by not providing template, Swashbuckle tries to use default map for both of them. hence conflict occurs.

You can also merge methods with same endpoints to one with optional parameters. Example of implementation tested in net core 5 project:
services.AddSwaggerGen(c =>
{
c.ResolveConflictingActions(apiDescriptions =>
{
var descriptions = apiDescriptions as ApiDescription[] ?? apiDescriptions.ToArray();
var first = descriptions.First(); // build relative to the 1st method
var parameters = descriptions.SelectMany(d => d.ParameterDescriptions).ToList();
first.ParameterDescriptions.Clear();
// add parameters and make them optional
foreach (var parameter in parameters)
if (first.ParameterDescriptions.All(x => x.Name != parameter.Name))
{
first.ParameterDescriptions.Add(new ApiParameterDescription
{
ModelMetadata = parameter.ModelMetadata,
Name = parameter.Name,
ParameterDescriptor = parameter.ParameterDescriptor,
Source = parameter.Source,
IsRequired = false,
DefaultValue = null
});
}
return first;
});
});

If the method name are same then change the request method with parameter.
I changed the request method to following :
[HttpGet]
public string products()
{
// add other code
// ex. (return "products()";)
}
[HttpGet("{id}")]
public string products(int id)
{
// add other code
// ex. (return "products(int id)";)
}

This is how I specified the unique routes for method name
[HttpGet("~/GetWeatherForecast")]
Keeping it above the methods
[HttpGet("~/GetWeatherForecast")]
public int Get()
{
return Random.Next(5)
}
[HttpPost("~/InsertAddition")]
public int InsertAddition(int num1, int num2)
{
return num1 + num2;
}

Try adding both Route and HttpGet.
[HttpGet]
[Route(~/GetByIdAsync/{id})]
public async Task<DataStoreQuery> GetByIdAsync(int id)
[HttpGet]
[Route(~/GetAllAsync)]
public async Task<IEnumerable<DataStoreQuery>> GetAllAsync(string instanceName, string dbname, string userName, string userPass, bool isActive, DateTime? startCreatedDate, DateTime? endCreatedDate, DateTime? startModifiedDate, DateTime? endModifiedDate)

Related

How to capture the status code of an IActionResult Method return in a variable C#

I have the following method:
public IActionResult DoSomeThing()
{
try
{
Some code...
}
catch (Exception)
{
return BadRequest();
}
return Ok();
}
I have another method from which I must capture what the DomeSomething () method returns to me in a variable:
public void OtherMethod()
{
var result = DoSomeThing();
if (result == Here I need to compare with the result, for example if it is a 200 result or Ok, do the action)
{
Do an action...
}
}
I need to extract the status code, for example result == 200 for it to execute an action.
We usually use HttpClient to perform such operations. You can see my example below.
In your Startup, add
services.AddHttpClient();
In your controller:
private readonly IHttpClientFactory _clientFactory;
public HomeController(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public IActionResult DoSomeThing()
{
return Ok();
}
public void OtherMethod()
{
var URL = "https://localhost:xxxx/home/DoSomeThing";
var message = new HttpRequestMessage(HttpMethod.Get, URL);
var client = _clientFactory.CreateClient();
var response = client.Send(message);
if (response.IsSuccessStatusCode)
{
//...
}
else
{
}
}
Test result:
You can see more about HttpClient here.

how to handle Extension methods in controller?

I really Didn't get proper Title for this question. Please correct it if its misleading.
I have a WebApi controller, where there are multiple validation check are there. Controller sample code
public async Task<IActionResult> UploadFile(IFormFile file)
{
try
{
return file.IsValid();
//some more Functionality
}
}
Here Isvalid is a Extension method where the code is as follows
public static IActionResult PrepareResult(this ControllerBase controller, IFormFile file)
{
if (file== null)
{
return controller.Badrequest("No data sent");
}
return controller.Ok();
}
Issue:- In current scenario , if the file is Null then Extension method will be returning Badrequest() & the same will be returned to the client. But if file is not null then It's going to return Ok() & same will be returned to the Clint, where as i have more code to execute(i.e.//some more Functionality).
I don't want to return controller.Ok(), so that for positive scenario i can continue with my remaining code.
NB:- i don't want to assign to any variable & check with If condition. In order to avoid if condition only i am using extension methods.
Not sure why you don't want to assign varaibles and avoid if condition as this is the most efficient way. You can use exception handling, though that comes with a performance cost.
public static void EnsureFileIsValid(this IFormFile file)
{
if(file == null) { throw new InvalidOperationException("No data sent"); }
}
public async Task<IActionResult> UploadFile(IFormFile file)
{
try
{
file.EnsureFileIsValid();
return Ok();
}
catch(InvalidOperationException ex)
{
return BadRequest(ex.Message);
}
}
You can pass a action to your method like :
public static IActionResult PrepareResult(this ControllerBase controller, IFormFile file, Action<IFormFile> work)
{
if (file == null)
{
return controller.Badrequest("No data sent");
}
work(file);
return controller.Ok();
}
In you action, the use is :
public class FilesController : ControllerBase
{
[HttpPost]
public IActionResult UploadFile([FromServices]IFileService fileService, IFormFile file)
{
return Ok(PrepareResult(file, fileService.Upload));
}
}
But maybe you can consider to use validation.
In validation step, you enforce a parameter isn't null with RequiredAttribute.
ApiControllerAttribute enforce the validation before the action is called. If the validation fail, then ASP.NET Core return directly BadRequest and the action isn't called.
In this example, if the parameter file is null then the action isn't called and it return BadRequest :
[ApiController]
public class FilesController : ControllerBase
{
[HttpPost]
public IActionResult UploadFile([Required]IFormFile file)
{
//Do something
return Ok();
}
[HttpPut]
public IActionResult UploadFileBis([Required] IFormFile file)
{
//Do something
return Ok();
}
}
PS : You can use [ApiControllerAttribute] at assembly level, it will be enable to all controllers in assembly :
[assembly: ApiController]
If you want to verify conditions and define the error response in the same lower layer class, here is a solution.
First, let's create a custom exception that will hold the http status code and message to return:
// serialization implementation redacted
public class InfrastructureException : Exception
{
public HttpStatusCode HttpStatusCode { get; }
public InfrastructureException(HttpStatusCode code, string message) : base(message)
{
HttpStatusCode = code;
}
}
We need a class to handle the response serialization:
public class ExceptionResponse
{
public int StatusCode { get; set; }
public string Message { get; set; }
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
Then create a middleware that handle exceptions:
public class InfrastructureExceptionMiddleware
{
private readonly RequestDelegate next;
public InfrastructureExceptionMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task InvokeAsync(HttpContext httpContext, IHostEnvironment hostEnvironment)
{
try
{
await this.next(httpContext);
}
catch (Exception ex)
{
await HandleExceptionAsync(httpContext, ex);
}
}
private Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
ExceptionResponse response = exception is InfrastructureException infrastructureException
? new ExceptionResponse()
{
StatusCode = (int)infrastructureException.HttpStatusCode,
Message = infrastructureException.Message
}
: new ExceptionResponse()
{
StatusCode = (int)HttpStatusCode.InternalServerError,
Message = ReasonPhrases.GetReasonPhrase(context.Response.StatusCode)
};
return context.Response.WriteAsync(response.ToString());
}
}
Now, we need to register our middleware:
public class Startup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// redacted
app.UseMiddleware<InfrastructureExceptionMiddleware>();
// redacted
}
}
In the controller, we delegate the validation to the extension method:
public async Task<IActionResult> UploadFile(IFormFile file)
{
file.IsValid();
// now you can consider file is valid
}
Finally, in the extension method, we throw the exception:
public static void IsValid(this IFormFile file)
{
if(file == null)
{
throw new InfrastructureException(HttpStatusCode.BadRequest, "No data sent");
}
if(...) // condition for Http NotFound
{
throw new InfrastructureException(HttpStatusCode.NotFound, "Data not found");
}
// other validation conditions
}

Middleware using the parameters that is sent from the client side and allow or not the interaction with the endpoint depending on these parameters

I am using c# core 3.1
I have several endpoints with a structure similar to this:
[HttpPost]
public async Task<ActionResult<Usuario>> Post([FromBody] User user)
{
context.User.Add(user);
try
{
await context.SaveChangesAsync();
}
catch (Exception ex)
{
...
}
return Ok();
}
The user sends an object like this:
{"rol": "Administrator", "name":"pedro"}
I would like to validate that if it contains a certain value, allow to continue with the endpoint logic or otherwise do not allow it.
for example I want to make a verification that if rol= Administrator allows to continue with my endpoint.
I am very confused but I don't know if something like this exists but it works as a middleware where can I get the data that is sent from the client side to perform validations:
[HttpPost]
[MyCustomMiddleWare]
.
.
public class MyCustomMiddleWare
{
.
.
if (dataFromClientSide.rol== "Administrator")
{
continue
}
else{
return Ok(new { message:"Not has permission" })
}
}
It looks like you just need some modelvalidation like bellow:
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
[ApiController]
[Route("api/[controller]")]
public class YourController : ControllerBase
{
public class User
{
[RegularExpression(pattern: "Administrator", ErrorMessage = "Your error message.")]
public string Role { get; set; }
public string Name { get; set; }
}
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody] User user)
{
if (this.ModelState.IsValid)
{
return this.ValidationProblem();
}
// Do something here;
return this.Ok();
}
}
But if you insist to do it with middleware it will looks like this bellow:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMiddleware<UserRoleMiddleware>();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
public class UserRoleMiddleware
{
private readonly RequestDelegate next;
public UserRoleMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext httpContext)
{
using var reader = new StreamReader(httpContext.Request.Body);
var body = await reader.ReadToEndAsync();
var user = JsonConvert.DeserializeObject<User>(body);
if (user != null && !user.Role.Equals("Administrator", StringComparison.OrdinalIgnoreCase))
{
// Redirect or do somethings else.
}
await next(httpContext);
}
}
To validate specific endpoints just implement ActionFilterAttribute:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<AdministratorAttribute>();
}
public class AdministratorAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.ActionArguments.TryGetValue("user", out object user))
{
if (!(user as User).Role.Equals("Administrator"))
{
// Redirect or something
}
}
base.OnActionExecuting(context);
}
}
[HttpPost]
[Administrator]
public async Task<IActionResult> PostAsync([FromBody] User user)
{
return this.Ok();
}

How to return a value instead of a status if request is not authorised?

[authorise]
public string Get()
{
return "value1";
}
if I am not authorised it will return a status of 401 not authorised.
can it return a value such as json "{status:false,code:"401"}". ?
According to your description, I suggest you could try to use custommiddleware to achieve your requirement.
You could captured the 401 error in middleware and then rewrite the response body to {status:false,code:"401"}
More details, you could add below codes into Configure method above the app.UseAuthentication();:
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == 401)
{
await context.Response.WriteAsync("{status:false,code:'401'}");
}
});
Result:
You can create a custom authorize attribute using IAsyncAuthorizationFilter.
public class CustomAuthorizeFilter : IAsyncAuthorizationFilter
{
public AuthorizationPolicy Policy { get; }
public CustomAuthorizeFilter(AuthorizationPolicy policy)
{
Policy = policy ?? throw new ArgumentNullException(nameof(policy));
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// Allow Anonymous skips all authorization
if (context.Filters.Any(item =&gt; item is IAllowAnonymousFilter))
{
return;
}
var policyEvaluator = context.HttpContext.RequestServices.GetRequiredService&lt;IPolicyEvaluator&gt;();
var authenticateResult = await policyEvaluator.AuthenticateAsync(Policy, context.HttpContext);
var authorizeResult = await policyEvaluator.AuthorizeAsync(Policy, authenticateResult, context.HttpContext, context);
if (authorizeResult.Challenged)
{
// Return custom 401 result
context.Result = new CustomUnauthorizedResult("Authorization failed.");
}
else if (authorizeResult.Forbidden)
{
// Return default 403 result
context.Result = new ForbidResult(Policy.AuthenticationSchemes.ToArray());
}
}
}
public class CustomUnauthorizedResult : JsonResult
{
public CustomUnauthorizedResult(string message)
: base(new CustomError(message))
{
StatusCode = StatusCodes.Status401Unauthorized;
}
}
public class CustomError
{
public string Error { get; }
public CustomError(string message)
{
Error = message;
}
}
The code in this article does exactly what you want. click here
can it return a value such as json "{status:false,code:"401"}". ?
Sure, you can.
[ApiController]
[Produces("application/json")]
public class TestController : ControllerBase
{
public IActionResult Get()
{
if (User.Identity.IsAuthenticated)
{
return new OkObjectResult(new { status: true, code: 200 });
}
return new OkObjectResult(new { status: false, code: 401 });
}
}
But notice that, the request will return with the real status code 200 (OK)
You can also use UnauthorizedObjectResult like #vivek's comment:
return new UnauthorizedObjectResult(new { status: false, code: 401 });
You can return the below if using Asp.Net Core 3.1, It returns UnauthorizedObjectResult.
return Unauthorized(new { status: false, code: 401 });

Refactor Error Handling in WebAPI Controller

I have lots of controllers methods in WebAPI similar to the following:
public IHttpActionResult Delete(int id)
{
var command = new DeleteItemCommand() { Id = id };
try
{
_deleteCommandHandler.Handle(command);
}
catch (CommandHandlerResourceNotFoundException)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
catch(CommandHandlerException)
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
// More catches etc...
return Ok();
}
The command handlers (in this instance _deleteCommandHandler) is injected earlier in the execution and the commands may be built in the method or using WebApi's automatic method.
What I would like to do is to encapsulate the try/catch error handling in a private method and end up with a controller similar to:
public IHttpActionResult Delete(int id)
{
var command = new DeleteItemCommand() { Id = id };
return ExecuteCommand(x => _deleteCommandHandler.Handle(command));
}
I'm not sure what the signature of the private ExecuteCommand method should be though.
I think you can Invoke your action in a method like this:
public IHttpActionResult Delete(int id)
{
return ExecuteCommand(() => {
var command = new DeleteItemCommand() { Id = id };
_deleteCommandHandler.Handle(command);
});
}
private IHttpActionResult ExecuteCommand(Action action)
{
try
{
action.Invoke();
//or: action();
}
catch (CommandHandlerResourceNotFoundException)
{
return HttpResponseException(HttpStatusCode.NotFound);
}
catch (CommandHandlerException)
{
return HttpResponseException(HttpStatusCode.InternalServerError);
}
return Ok();
}
A good reference for HttpResponseException.
I would create a custom error handler filter, and handle all possible errors there in a centralized form. That way you can just throw whatever exception from the action methods, and then they will be caught at the filter where you can handle them and change the response accordingly.
public class NotImplExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is NotImplementedException)
{
context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
}
}
}
The example is taken from this article where you can find the concept in more detail.
Here's a solution similar to shA.t's answer, but the exceptions are mapped in a dictionary and the try/catch logic is in an extension method:
public class TestController:ApiController
{
public IHttpActionResult Delete(int id)
{
return ExecuteCommand(() => {
var command = new DeleteItemCommand() { Id = id };
_deleteCommandHandler.Handle(command);
});
}
private IHttpActionResult ExecuteCommand(Action action)
{
return action.SafeInvoke();
}
}
public static class ActionExtensions
{
private static readonly Dictionary<Type, HttpStatusCode> _exceptionToStatusCodeLookup = new Dictionary<Type, HttpStatusCode>
{
{typeof(CommandHandlerResourceNotFoundException), HttpStatusCode.NotFound },
{typeof(CommandHandlerException), HttpStatusCode.InternalServerError },
};
public static IHttpActionResult SafeInvoke(this Action action)
{
try
{
action();
}
catch (Exception ex)
{
var statusCode = _exceptionToStatusCodeLookup.ContainsKey(ex.GetType()) ? _exceptionToStatusCodeLookup[ex.GetType()] : HttpStatusCode.InternalServerError;
return new HttpResponseException(statusCode);
}
return new OkResult();
}
}

Categories