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.
Related
I'm trying to create multiple level routes in asp.net core such as:
api/cities
api/cities/{id}
api/cities/date/{date}
The problem is, when I try and use anything longer than the api/cities/{id} I just get a 404.
My controller:
[Route("api/[controller]")]
public class CitiesController : Controller
{
private ICityRepository _repository;
public CitiesController(ICityRepository repository)
{
_repository = repository;
}
// GET: api/cities
[HttpGet]
public IEnumerable<City> Get()
{
IEnumerable<City> results = _repository.GetCities();
return results;
}
//api/cities/date/{date}
[HttpGet]
[Route("date/{date}")]
public IEnumerable<City> Get2(string date)
{
return _repository.GetCitiesByDate(date);
}
// GET api/cities/5
[HttpGet("{id: int}")]
public string Get(int id)
{
return "value";
}
}
What do I need to do to get longer routes to work under this controller?
Edit:
I see the documentation here: https://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
It says that you can have routes like:
/orders/1
/orders/pending
/orders/2013/06/16
And have all three route separately. But it doesn't seem to provide example for how you do that specifically.
I can see the problem with the route as you cannot have "/" as part of the string.
Try passing the date as 2013_06_16 not 2013/06/16 or change the route to have date/{year}/{month}/{day}
example:
[Route("api/[controller]")]
public class CitiesController : Controller
{
private ICityRepository _repository;
public CitiesController(ICityRepository repository)
{
_repository = repository;
}
// GET: api/cities
[HttpGet]
public IEnumerable<City> Get()
{
IEnumerable<City> results = _repository.GetCities();
return results;
}
//api/cities/date/2016/06/16
[HttpGet]
[Route("date/{year}/{month}/{day}")]
public IEnumerable<City> Get2(string year,string month,string day)
{
string date = year + "/" + month + "/" + day;
return _repository.GetCitiesByDate(date);
}
// GET api/cities/5
[Route("{id: int}")]
[HttpGet]
public string Get(int id)
{
return "value";
}
//api/cities/pending
[Route("{text}"]
[HttpGet]
public string Get(string text)
{
return "value";
}
}
Hope this help.
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
i have an Mvc controller which looks like:
public class PaymentController : Controller
{
public ActionResult ApiReturn()
{
var notify_id = Request.QueryString["notify_id"];
return null;
}
}
how do i transfer the query string "notify_id" when i do unit test for this ApiReturn, my unit test for this action is like below:
[TestClass]
public class PaymentControllerUnitTest
{
[TestMethod]
public void TestApiReturn()
{
var paymentController = new PaymentController();
paymentController.ApiReturn();
}
}
I have a project with 8 layers
I want to edit a record ; for example:
In repository layer:
1:in IRepository:
IEnumerable GetArea(int id);
void Edit(CRM_Area crm);
2: in repository:
public IEnumerable<CRM_Area> GetArea(int ID)
{
return _db.CrmAreas.Where(x=>x.ID==ID);
}
public void Edit(CRM_Area crmArea)
{
_db.Entry(crmArea).State=EntityState.Modified;
}
and in services layer:
1:in Iservices:
void Delete(int ID);
IEnumerable<_CRM_AreaList> GetArea(int ID);
void Edit(_CRM_AreaEdit crmArea);
2: in Services:
public IEnumerable<_CRM_AreaList> GetArea(int ID)
{
return _areaRepository.GetArea(ID).Select(Mapper.Map<CRM_Area, _CRM_AreaList>);
}
public void Edit(_CRM_AreaEdit crmAreaEdit)
{
var editArea = Mapper.Map<_CRM_AreaEdit, CRM_Area>(crmAreaEdit);
_areaRepository.Edit(editArea);
}
and in web layer in controller:
[HttpGet]
public ActionResult Edit(int ID)
{
return View(_areaServices.GetArea(ID));
}
[HttpPost]
public ActionResult Edit(_CRM_AreaEdit crmAreaEdit)
{
_areaServices.Edit(crmAreaEdit);
return RedirectToAction("List");
}
But it doesn't work. Please help me - How can I edit and where is my mistake?
When I click on edit, the model item passed into the dictionary is of type 'System.Linq.Enumerable+WhereSelectEnumerableIterator`2[CRM.Web.Models.CRM_Area,CRM.Web.ViewModel.Area._CRM_AreaList]', but this dictionary requires a model item of type 'CRM.Web.ViewModel.Area._CRM_AreaEdit'.
In your repository try to save the changes.
Always keep in mind, you need to save the changes after you edit it.
public void Edit(CRM_Area crmArea)
{
_db.Entry(crmArea).State=EntityState.Modified;
_db.SaveChanges();
}
I write this code in several places and always repeat this logic:
public ActionResult MyMethod(MyModel collection)
{
if (!ModelState.IsValid)
{
return Json(false);//to read it from javascript, it's always equal
}
else
{
try
{
//logic here
return Json(true);//or Json(false);
}
catch
{
return Json(false);//to read it from javascript, it's always equal
}
}
}
Is there any way using action filters, not to be repeating the try-catch, ask if the model is valid and return Json(false) as ActionResult?
To conform with REST, you should return http bad request 400 to indicate that the request is malformed (model is invalid) instead of returning Json(false).
Try this attribute from asp.net official site for web api:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
A version for asp.net mvc could be like this:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.Controller.ViewData.ModelState.IsValid)
{
filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
}
}
If you want to do this in MVC6 or Mvc Core and without specifying your attribute on all of your Action methods then this is how you do it.
First create your ActionFilter
public class ModelStateValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting( ActionExecutingContext context )
{
if ( context.HttpContext.Request.Method == "POST" && !context.ModelState.IsValid )
context.Result = new BadRequestObjectResult( context.ModelState );
}
}
Now create a convention in which you will apply this ActionFilter to all of your controllers.
public class ModelStateValidatorConvension : IApplicationModelConvention
{
public void Apply( ApplicationModel application )
{
foreach ( var controllerModel in application.Controllers )
{
controllerModel.Filters.Add( new ModelStateValidationFilterAttribute() );
}
}
}
And the last thing is to register this convention in MVC
public void ConfigureServices( IServiceCollection services )
{
services.Configure<MvcOptions>( x => x.Conventions.Add( new ModelStateValidatorConvension() ) );
}
Starting from ASP.Net Core 2.1, the [ApiController] attribute will trigger automatic model validation:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
Consequently, the following code is unnecessary in action methods or custom ActionFilterAttribute:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Source: https://learn.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-2.1#automatic-http-400-responses-1
Here is how to use the code from Khanh TO (from asp.net official site):
To apply this filter to all Web API controllers, add an instance of the filter to the HttpConfiguration.Filters collection during configuration:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new ValidateModelAttribute());
// ...
}
}
Another option is to set the filter as an attribute on individual controllers or controller actions:
public class ProductsController : ApiController
{
[ValidateModel]
public HttpResponseMessage Post(Product product)
{
// ...
}
}
public class ValidateModelStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new ViewResult();
}
}
}