0

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?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
javaTopics
  • 11
  • 3
  • 1
    The Startup class looks a bit messy and has mixed configuration that I don't quite understand. The first rule of middlware is that order matters. It seems you're turning off endpoint routing and using the old MVC routing, why is that? What URLs do you want to map to these controller actions? – davidfowl Aug 22 '22 at 14:28
  • check [this](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-6.0#request-handling). according to this it is possible to map same url for different action method. you have tried many things . you need to cleanup your code and keep only required things. – CodingMytra Aug 22 '22 at 17:46
  • Maybe [here](https://stackoverflow.com/questions/56582449/http-verbs-put-and-delete-405-method-not-allowed-how-to-allow/56617762#56617762) is the answer you want. – Chen Aug 23 '22 at 09:45

1 Answers1

0

I'm not sure if this is the solution to your issue but .NET 6.0+ does not automatically generate the startup.cs file anymore.

SoleKopes
  • 25
  • 4
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Aug 25 '22 at 18:16