asp.net WebApi 2 routing query string json with route parameter - c#

How can i implement the following routing scheme
http://localhost/vitualdir/prefix/{id}/methodname?{encoded json defenition of object}
using asp.net webapi 2 route attributes ?
My suggestions are :
firstly: add [RoutePrefix("prefix")] to controller
secondly : implement the following defenition:
[Route("~/{id}/methodname")]
[HttpGet]
public async Task<IHttpActionResult> methodname([FromUri] JsonObjectFromUri object, int id)
{
But that code is not working as i want. Could you help me with it ?

'~' in the Route specified on an Action, overrides the Route prefix.
Try removing it. It should work.
Refer http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2#prefixes
eg.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Routing;
namespace MvcApplication2.Controllers
{
public class TestClass
{
public string Name { get; set; }
public int Age { get; set; }
}
[RoutePrefix("prefix")]
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[Route("{id}/methodname")]
public string Get(int id, [FromUri] TestClass objectFromUri)
{
return "value";
}
// POST api/values
public void Post([FromBody]string value)
{
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
public void Delete(int id)
{
}
}
}
Now if you pass the Properties in the TestClass as url parameters, WebAPI will automatically bind them to the objectFromUri object.
http://localhost:39200/prefix/1/methodname?name=ram&age=10

Related

Why swagger return "Failed to load api definition"?

This is my code.
I'm trying to overload GET with 2 function :
With one parameter
With two parameter
I'm getting Swagger error "Failed to load API definition". Why ?
[Route("api/[controller]")]
[ApiController]
public class HospitalizedController : ControllerBase
{
[HttpGet("")]
public string Get(string MedicID)
{
string jsonData;
string connString = gsUtils.GetDbConnectionString();
// dosomething
}
[HttpGet("")]
public string Get(string MedicID, string PatientID)
{
string jsonData;
string connString = gsUtils.GetDbConnectionString();
//do something
}
}
The error "Failed to load API definition" occurs because the two methods are on the same Route.
You can specify a more specific route to distinguish them, like this:
[Route("api/[controller]")]
[ApiController]
public class HospitalizedController : ControllerBase
{
[HttpGet]
[Route("{medicId}")]
public string Get(string medicID)
{
}
[HttpGet]
[Route("{medicId}/patients/{patientId}")]
public string Get(string medicID, string patientID)
{
}
}

Multiple actions were found that match the request - Net Framework 4.6.1

I have the following endpoints in my project
A -> http://localhost:8089/products/
B -> http://localhost:8089/products/{id}
I need to add the following:
C -> http://localhost:8089/products/{id}/track
but when I run the new endpoint I get the following error (endpoint B is also affected):
Multiple actions were found that match the request:
GetProduct on type ProductsApi.Controllers.ProductsController
GetProductTracking on type ProductsApi.Controllers.ProductsController
en System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.SelectAction(HttpControllerContext controllerContext)
en System.Web.Http.Controllers.ApiControllerActionSelector.SelectAction(HttpControllerContext controllerContext)
en System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
en System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__15.MoveNext()
My controller has:
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
namespace ProductsApi.Controllers
{
[Route("controller")]
[Authorize]
public class ProductsController : BaseController
{
private readonly IProductService _ProductService;
public ProductsController(IProductService ProductService)
{
_ProductService = ProductService;
}
[HttpGet]
public async Task<IHttpActionResult> ListProducts(string startDate = "", string endDate = "", string brand = "")
{
ProductFilterRequest filterProductRequest = new ProductFilterRequest(startDate, endDate, brand);
var result = await _ProductService.ListProducts(filterProductRequest);
return Ok(result);
}
[HttpGet]
public async Task<IHttpActionResult> GetProduct(int id)
{
var result = await _ProductService.GetProductDetail(id);
return Ok(result);
}
[HttpGet()]
[Route("{id}/track")]
public async Task<IHttpActionResult> GetProductTracking(int id)
{
var result = await _ProductService.GetProductTracking(id);
return Ok(result);
}
}
}
Startup.cs
using Microsoft.Owin;
using System.Web.Http;
using System.Configuration;
[assembly: OwinStartup(typeof(Product.Api.Startup))]
namespace Product.Api
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
....
// configure web api
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
app.UseWebApi(config);
}
}
}
Global.asax.cs
using Newtonsoft.Json.Serialization;
...
using System.Web.Http;
namespace Product.Api
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AutoMapperConfig.Initialize();
GlobalConfiguration.Configure(WebApiConfig.Register);
...
}
}
}
WebApiConfig.cs
I have configured the routing and I have tried making modifications in the routetemplate but without any result.
using System.Web.Http;
namespace Product.Api
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}/{name}",
defaults: new
{
id = RouteParameter.Optional,
name=RouteParameter.Optional
}
);
}
}
}
Try this
[HttpGet("track/{id}")]
public async Task<IHttpActionResult> GetProductTracking(int id)
{
var result = await _ProductService.GetProductTracking(id);
return Ok(result);
}
In Startup.cs
Comment the code below, this setting is not being applied correctly
using Microsoft.Owin;
using System.Web.Http;
using System.Configuration;
[assembly: OwinStartup(typeof(Product.Api.Startup))]
namespace Product.Api
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
....
// configure web api
//var config = new HttpConfiguration();
//config.MapHttpAttributeRoutes();
//app.UseWebApi(config);
}
}
}
In WebApiConfig.cs you need add below line
config.MapHttpAttributeRoutes();
Finally in your controller, You can set a common prefix for an entire controller by using the [RoutePrefix] attribute and for actions add [Route] attribute
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
namespace ProductsApi.Controllers
{
[RoutePrefix("orders")]
[Authorize]
public class ProductsController : BaseController
{
private readonly IProductService _ProductService;
public ProductsController(IProductService ProductService)
{
_ProductService = ProductService;
}
public OrdersController()
{
}
[HttpGet]
public async Task<IHttpActionResult> ListProducts(string startDate = "", string endDate = "", string brand = "")
{
ProductFilterRequest filterProductRequest = new ProductFilterRequest(startDate, endDate, brand);
var result = await _ProductService.ListProducts(filterProductRequest);
return Ok(result);
}
[HttpGet]
public async Task<IHttpActionResult> GetProduct(int id)
{
var result = await _ProductService.GetProductDetail(id);
return Ok(result);
}
[HttpGet()]
[Route("{id}/track")]
public async Task<IHttpActionResult> GetProductTracking(int id)
{
var result = await _ProductService.GetProductTracking(id);
return Ok(result);
}
}
}
See more in https://learn.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
[Route("track/{id}")]
Could you try put the variable {id} at the end of the address?

Web Api Repository/ValueController problem

I develop web application. I started from Web Api with Entity Framework. I want realize CRUD function.
Read function works fine
But problem with Create, Update, Delete, could you check it and tell me, what I do wrong?
I attach 2 blocks of code, I don't know how I can realize 1st repository (Mistake in C,U,D - function) in controller.
I don't have enough experience with web api, could you tell me, how I need setting Repository(only for realize create, delete, update) file and after realize it in Value Controller
I need your advice about create, update, delete - function
Customer Repos
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace WebAPI
{
public class CustomerRepository
{
public IQueryable<Customer> GetAllCustomers()
{
DevelopersEntities dev = new DevelopersEntities();
return dev.Customers;
}
public IQueryable<Customer> GetAllCustomers(int id)
{
DevelopersEntities dev = new DevelopersEntities();
return dev.Customers.Where(c=>c.Id==id).Select(e=>e);
}
public IQueryable<Customer> DeleteCustomer(int id)
{
DevelopersEntities dev = new DevelopersEntities();
var cus = dev.Customers.Where(s => s.Id == id).FirstOrDefault();
dev.Entry(cus).State = System.Data.Entity.EntityState.Deleted;
dev.SaveChanges();
return cus;
}
}
public IQueryable<Customer> CreateCustomer(Customer customer)
{
DevelopersEntities dev = new DevelopersEntities();
dev.Customers.Add(new Customer()
{
Id = customer.Id,
Name = customer.Name
});
dev.SaveChanges();
return Ok();
}
public IQueryable<Customer> UpdateCustomer(Customer customer)
{
DevelopersEntities dev = new DevelopersEntities();
var cus = dev.Customers.Where(s => s.Id == customer.Id).FirstOrDefault();
cus.Id = customer.Id;
cus.Name = customer.Name;
dev.SaveChanges();
return Ok();
}
}
Values Controller
using DevelopersWeb.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using WebAPI;
namespace DevelopersWeb.Controllers
{
public class ValuesController : ApiController
{
ModelFactory _modelFactory;
public ValuesController()
{
_modelFactory = new ModelFactory();
}
// GET api/values
public IEnumerable<CustomerModel> Get()
{
CustomerRepository cr = new CustomerRepository();
return cr.GetAllCustomers().ToList().Select(c=> _modelFactory.Create(c));
}
// GET api/values/5
public string Get(int id)
{
return "xxx";
}
// POST api/values
public void Post([FromBody] CustomerModel customerModel)
{
CustomerRepository cr = new CustomerRepository();
cr.CreateCusomer(customer);
return Ok();
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
public void Delete(int id)
{
}
}
}

Web API PUT Return Object with Proper HTTP Status Code

First, I want to say [HttpGet], [HttpGet("{id}")], and [HttpPost] is working correctly. However I'm running into a challenge with [HttpPut], and almost everywhere I look, the solution is to return without a status code.
I'm using visual studio 2019 with a project type of "ASP.NET Core Web Application" and "API" (ASP.NET Core 3.1).
I'm also using a secondary project in the same visual studio solution with the type(C#) "Class Library (.NET Standard)".
I'm using Postman to test the http request calls.
The following (Additional) NuGet Packages are required to be installed:
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.InMemory
System.ComponentModel.Annotations
The Solution Explorer:
There is a lot of code to cover with the .net core, and I will show it all here (as far as what has changed).
The Project "CoreStudy.Api" Code:
Startup.cs
using CoreStudy.Data.Context;
using CoreStudy.Data.Repository;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace CoreStudy.Api
{
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.AddControllers();
services.AddScoped<PeopleRepository>(); // add this line
services.AddDbContext<PersonContext>(opt => opt.UseInMemoryDatabase("PeopleInventory")); // add this line, requires NuGet package "Microsoft.EntityFrameworkCore.InMemory"
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
PeopleController.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using CoreStudy.Data.Repository;
using CoreStudy.Data.Models;
using Microsoft.AspNetCore.Http;
namespace CoreStudy.Api.Controllers
{
[Route("people")]
[ApiController]
public class PeopleController : ControllerBase
{
private readonly PeopleRepository _repository;
public PeopleController(PeopleRepository repository)
{
_repository = repository;
}
[HttpGet]
[ProducesResponseType(typeof(List<PersonModel>), StatusCodes.Status200OK)]
public IActionResult GetPeople()
{
return Ok(_repository.GetPeople());
}
[HttpGet("{id}")]
[ProducesResponseType(typeof(PersonModel), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetPersonById(int id)
{
var person = _repository.GetPersonById(id);
if (person == null)
{
return NotFound();
}
return Ok(person);
}
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> AddPersonAsync([FromBody] PersonModel person)
{
if((_repository.GetPersonById(person.id) != null) || String.IsNullOrWhiteSpace(person.name))
{
return BadRequest();
}
int peopleAdded = await _repository.AddPersonAsync(person);
return CreatedAtAction(nameof(GetPersonById), new { person.id }, person);
}
[HttpPut]
[ProducesResponseType(typeof(PersonModel), StatusCodes.Status202Accepted)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> ChangePersonNameByIdAsync([FromBody] PersonModel person)
{
if (_repository.GetPersonById(person.id) == null)
{
return NotFound();
}
else if (String.IsNullOrWhiteSpace(person.name))
{
return BadRequest();
}
PersonModel updatedPerson = await _repository.ChangePersonNameAsync(person);
return Ok(updatedPerson);
}
}
}
The Project "CoreStudy.Data" Code:
PersonContext.cs
using Microsoft.EntityFrameworkCore;
using CoreStudy.Data.Models;
namespace CoreStudy.Data.Context
{
public class PersonContext : DbContext
{
public PersonContext(DbContextOptions<PersonContext> options) : base(options)
{
}
public DbSet<PersonModel> people { get; set; }
}
}
PersonModel.cs
using System.ComponentModel.DataAnnotations;
namespace CoreStudy.Data.Models
{
public class PersonModel
{
public int id { get; set; }
[Required]
public string name { get; set; }
public string position { get; set; }
public PersonModel() { }
public PersonModel(string name, string position)
{
this.name = name;
this.position = position;
}
public PersonModel(int id, string name, string position)
{
this.id = id;
this.name = name;
this.position = position;
}
}
}
PeopleRepository.cs
using System.Collections.Generic;
using CoreStudy.Data.Models;
using CoreStudy.Data.Context;
using System.Linq;
using System.Threading.Tasks;
namespace CoreStudy.Data.Repository
{
public class PeopleRepository
{
private readonly PersonContext context;
public PeopleRepository(PersonContext context)
{
this.context = context;
if (context.people.Count() == 0)
{
context.people.AddRange(
new PersonModel
{
name = "shaggy",
position = "screwball"
},
new PersonModel
{
name = "scooby",
position = "screwball dog"
},
new PersonModel
{
name = "fred",
position = "leader"
},
new PersonModel
{
name = "velma",
position = "smart one"
},
new PersonModel
{
name = "daphne",
position = "popular one"
});
context.SaveChanges();
}
}
public List<PersonModel> GetPeople()
{
return context.people.ToList();
}
public PersonModel GetPersonById(int id)
{
PersonModel person = context.people.Find(id); // null if not found
return person;
}
public async Task<int> AddPersonAsync(PersonModel person)
{
int rowsAffected = 0;
context.people.Add(person);
rowsAffected = await context.SaveChangesAsync();
return rowsAffected;
}
public async Task<PersonModel> ChangePersonNameAsync(PersonModel person)
{
context.people.Update(person);
await context.SaveChangesAsync();
return GetPersonById(person.id);
}
}
}
When trying to make a PUT request with postman, I get the following error:
The problem is either in one of these two snippets:
public async Task<PersonModel> ChangePersonNameAsync(PersonModel person)
{
context.people.Update(person); // I thought Update() would be best used here, but not sure
await context.SaveChangesAsync();
return GetPersonById(person.id);
}
[HttpPut]
[ProducesResponseType(typeof(PersonModel), StatusCodes.Status202Accepted)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> ChangePersonNameByIdAsync([FromBody] PersonModel person)
{
if (_repository.GetPersonById(person.id) == null)
{
return NotFound();
}
else if (String.IsNullOrWhiteSpace(person.name))
{
return BadRequest();
}
PersonModel updatedPerson = await _repository.ChangePersonNameAsync(person);
return Ok(updatedPerson); // not sure if I should be using Ok() for a PUT
}
If anyone could help me resolve this, myself (and I'm sure much of the internet) would thank you.
This already gets the entity:
if (_repository.GetPersonById(person.id) == null) { ... }
So you need to take that result:
var personDB = _repository.GetPersonById(person.id);
Then check if the variable is null
if(personDB != null) { ... }
Then you will need to change the personDB properties values with the person (from the PUT parameter) values.
After the line
if (_repository.GetPersonById(person.id) == null)
The person entity is already being tracked by the DbContext.
You don't need the extra repository layer at all. Your PersonContext already is a perfectly good repository. Just run:
context.people.Update(person);
await context.SaveChangesAsync();
return Ok(person);
in your controller.

Multiple Get actions with different attribute routing?

If I design my controller in such a way:
public class ItemController : ApiController
{
[HttpGet]
[RoutePrefix("item/dosomething")]
public void DoSomething(Item item)
{ }
[HttpGet]
[RoutePrefix("item/dosomethingnicer")]
public void DoSomethingNicer(Item item)
{ }
[HttpGet]
[RoutePrefix("item/dosomethingelse")]
public void DoSomethingElse(Item item)
{ }
}
Would this work?
I would expect a structure more akin to this:
[RoutePrefix("item")]
public class ItemController : ApiController
{
[HttpGet]
[Route("dosomething")]
public void DoSomething(Item item)
{ }
[HttpGet]
[Route("dosomethingnicer")]
public void DoSomethingNicer(Item item)
{ }
[HttpGet]
[Route("dosomethingelse")]
public void DoSomethingElse(Item item)
{ }
}
I use Web Api 2 in this way in a lot of Controllers:
[HttpGet]
[Route("~/api/{version}/research/export")]
public IHttpActionResult Export(){
do stuff...
}
[HttpPost]
[Route("~/api/{version}/research/list")]
public IHttpActionResult List()
{
do stuff...
}
I use full api path description and it works with no problems.

Categories