I have been working for a few days on a little C# micro service project.
About the project:
- Project uses a SwaggerAPI
- Project uses Entity Framework
- Project version: .NET 6.0
- Project uses MVC architecture
My csproj file:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<Description>IO.Swagger</Description>
<Copyright>IO.Swagger</Copyright>
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PreserveCompilationContext>true</PreserveCompilationContext>
<AssemblyName>IO.Swagger</AssemblyName>
<PackageId>IO.Swagger</PackageId>
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNet.Mvc" Version="5.2.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.8" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.2.3" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.2.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.2.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.2.3" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.4" />
</ItemGroup>
</Project>
My StartUp.cs:
using System;
using System.IO;
using System.Web.Mvc;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
using IO.Swagger.Filters;
using IO.Swagger.Models;
using IO.Swagger.Controllers;
using Microsoft.AspNetCore.Mvc.Formatters;
using IO.Swagger.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Cors.Infrastructure;
namespace IO.Swagger
{
public class Startup
{
private readonly IWebHostEnvironment _hostingEnv;
private IConfiguration Configuration { get; }
public Startup(IWebHostEnvironment env, IConfiguration configuration)
{
_hostingEnv = env;
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
// Add MVC API Endpoints
services.AddControllers().AddNewtonsoftJson();
services.AddControllers();
services.AddHttpClient();
services.AddMvcCore(options =>
{
options.RequireHttpsPermanent = true;
options.RespectBrowserAcceptHeader = true;
});
services.AddSwaggerGen();
services.AddCors();
services.AddDbContext<TaskContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
services.AddControllersWithViews().AddNewtonsoftJson();
// services.AddRazorPages();
services.AddMvc(options =>
options.EnableEndpointRouting = false
);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(ep =>
{
ep.MapControllerRoute(
name: "DefaultApi",
pattern: "{controller}/{action}/{id?}",
defaults: new {controller = "DefaultApi", action = "Index"}
);
ep.MapControllers();
});
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("openapi.yaml", "My first API");
});
app.UseMvc();
}
}
}
My Controller Class:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Collections;
using System.ComponentModel.DataAnnotations;
using Swashbuckle.AspNetCore.Annotations;
using Swashbuckle.AspNetCore.SwaggerGen;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using IO.Swagger.Attributes;
using IO.Swagger.Data;
using IO.Swagger.Models;
namespace IO.Swagger.Controllers
{
[Route("{controller}/{action}/{id}")]
[ApiController]
public class DefaultApiController : ControllerBase
{
private readonly TaskContext _context;
public DefaultApiController(TaskContext taskContext)
{
this._context = taskContext;
}
/*
public ActionResult Index(string id)
{
return View();
}
*/
[HttpGet]
[Route("/v1/tasks")]
[ValidateModelState]
[SwaggerOperation("TasksGet")]
[SwaggerResponse(statusCode: 200, type: typeof(List<GetTask>), description: "A JSON array of tasks")]
public async Task<IActionResult> TasksGet()
{
var tasks = await _context.Tasks.ToListAsync();
return Ok(tasks);
}
[HttpPost("post")]
[Route("/v1/tasks")]
[ValidateModelState]
[SwaggerOperation("TasksPost")]
[SwaggerResponse(statusCode: 201, type: typeof(GetTask), description: "Successfully created", contentTypes: "application/json")]
public async Task<IActionResult> TasksPost([FromBody]PostTask body)
{
IO.Swagger.Models.Task task = createTask(body);
_context.Add(task);
await _context.SaveChangesAsync();
return Ok(task);
}
[HttpDelete]
[Route("/v1/tasks/{uuid}")]
[ActionName("TasksUuidDelete")]
[ValidateModelState]
[SwaggerOperation("TasksUuidDelete")]
[SwaggerResponse(statusCode: 200, type: typeof(GetTask), description: "Delete Task")]
public virtual async Task<IActionResult> TasksUuidDelete([FromRoute][Required]string uuid)
{
uuid = "{" + uuid + "}";
var taskToDelete = new IO.Swagger.Models.Task();
var allTasks = await _context.Tasks.ToArrayAsync();
foreach (var task in allTasks)
{
if(task.Uuid.Equals(uuid))
{
taskToDelete = task;
}
}
if(taskToDelete != null)
{
_context.Remove(taskToDelete);
await _context.SaveChangesAsync();
return Ok(taskToDelete);
} else
{
return NotFound();
}
}
}
[HttpGet]
[Route("/v1/tasks/{uuid}")]
[ActionName("TasksUuidGet")]
[ValidateModelState]
[SwaggerOperation("TasksUuidGet")]
[SwaggerResponse(statusCode: 200, type: typeof(GetTask), description: "Get Task")]
public async Task<IActionResult> TasksUuidGet([FromRoute][Required]string uuid)
{
uuid = "{" + uuid + "}";
var taskToFind = new IO.Swagger.Models.Task();
var allTasks = await _context.Tasks.ToArrayAsync();
foreach (var task in allTasks)
{
if(task.Uuid.Equals(uuid))
{
taskToFind = task;
}
}
if (taskToFind == null)
{
return NotFound();
}
await _context.SaveChangesAsync();
return Ok(taskToFind);
}
[HttpPut]
[Route("/v1/tasks/{uuid}")]
[ActionName("TasksUuidPut")]
[ValidateModelState]
[SwaggerOperation("TasksUuidPut")]
[SwaggerResponse(statusCode: 200, type: typeof(PostTask), description: "Replace Task")]
public async Task<IActionResult> TasksUuidPut([FromBody]PostTask body, [FromRoute][Required]string uuid)
{
uuid = "{" + uuid + "}";
var taskToUpdate = new IO.Swagger.Models.Task();
var allTasks = await _context.Tasks.ToArrayAsync();
foreach (var task in allTasks)
{
if(task.Uuid.Equals(uuid))
{
taskToUpdate = task;
taskToUpdate.Title = body.Title;
taskToUpdate.Description = body.Description;
}
}
await _context.SaveChangesAsync();
if (taskToUpdate != null)
{
return Ok(taskToUpdate);
} else
{
return NotFound();
}
}
private IO.Swagger.Models.Task createTask(PostTask body)
{
IO.Swagger.Models.Task taskForList = new IO.Swagger.Models.Task();
taskForList.Title = body.Title;
taskForList.Description = body.Description;
taskForList.Uuid = System.Guid.NewGuid().ToString("B");
return taskForList;
}
}
}
My problem is when I try to execute the HttpPut
, HttpDelete
methods with the cURL command, I get the following error:
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/1.1 'PUT' https://localhost:5001/v1/tasks/c18556a8-635f-4fd7-8aea-13ff7a9c42d1 application/json 74
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]Executing endpoint '405 HTTP Method Not Supported'
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint '405 HTTP Method Not Supported'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]Request finished HTTP/1.1 'PUT' https://localhost:5001/v1/tasks/c18556a8-635f-4fd7-8aea-13ff7a9c42d1 application/json 74 - 405 0 - 8.4272ms
But when I add to the HttpAttributes an ID like in HttpPost
it works fine, but I can't put an ID to two or more HttpAttributes with the same URL because then I get an multiple methods error. I guess the problem is that HttpPut, HttpGet and HttpDelete
have the same routing URL.
I read a lot about the routing but can't fix the problem.
What I have tried:
- Use one method for
HttpGet, HttpPut and HttpDelete
and use a switch-case for the different implementations - Use an ID for all HttpAttributes
- Different configurations with
app.UseEndpoints
- Tried to use different controller classes for the HttpActions
Nothing really helps.
My question is: how can I map different HttpActions with the same route URL?