Asp.Net WebAPI and AttributeRouting - no overloaded attribute constructor? - asp.net-web-api

I have just downloaded AttributeRouting NuGet package for WebAPI and having a problem within my controller.
I thought the way to use it was to have something like:
public class InboxController : ApiController
{
private IInboxService _inboxService;
public InboxController(IInboxService inboxService)
{
_inboxService = inboxService;
}
public IEnumerable<MessageModel> GetAll()
{
return _inboxService.GetAllMessages();
}
[HttpGet("Inbox/Count")]
public int GetInboxCount()
{
return _inboxService.GetMessageCount();
}
}
However I get the following error:
Error 2 'System.Web.Http.HttpGetAttribute' does not contain a constructor that takes 1 arguments
I need to get this up and running fairly quickly. Is there any reason why the HttpGet attribute doesn't have an overloaded constructor?
UPDATE
[GET("Inbox/EnquiryCount")]
public EnquiryCountModel GetEnquiryCounts()
{
var model = new EnquiryCountModel();
model.EnquiryCount = _inboxService.GetCustomerEnquiriesCount();
model.EnquiryResponseCount = _inboxService.GetCustomerEnquiryResponseCount();
return model;
}
In routes:
routes.MapHttpRoute("InboxEnquiryApi", "api/inbox/{action}", new { Controller = "Inbox" }, null, new WebApiAuthenticationHandler(GlobalConfiguration.Configuration));
When I hit the URL at 'api/inbox/EnquiryCount' I get the this error:
**No HTTP resource was found that matches the request URI 'http://localhost:49597/api/inbox/enquirycount'**

Attribute routing is supported in Web api 2
Here is the details: http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2

This syntax has been changed in newer versions of the webapi. The [HTTPPOST] is now standalone and there is a new attribute for the route aptly name ROUTE which takes the route url
eg.
[Route("GetRes/{month}")]

Related

ASP.NET Web API Action Routing

After a lengthy discussion in terms of how our API would need to behave due to limitations of the backend design, I'd like to have the following possibilities:
1. /students/251/employment <--- allow GET, PUT, POST
2. /students/251/employment/jobs <--- allow GET only
3. /students/251/employment/jobs/435 <--- allow all verbs
4. /students/251/employment/internships <--- allow GET only
5. /students/251/employment/internships/664 <--- allow all verbs
These cases are working for GET requests. I'm struggling when I try to do a PUT request for case #1 and #3:
Case #1 Error
No HTTP resource was found that matches the request URI '/students/251/employment/221'.,
No action was found on the controller 'Employment' that matches the name '221'.
Case #3 Error
The requested resource does not support http method 'PUT'.
Here's an abridged version of my controller methods:
public ApiEmploymentGetResult Get(long id) {
// code omitted
}
[HttpGet]
public IEnumerable<ApiJob> Jobs(long id) {
// code omitted
}
[HttpGet]
public IEnumerable<ApiOwnVenture> OwnVenture(long id) {
// code omitted
}
public void Put(long id, MyModel model) {
// breaks before getting here
}
My routing looks like this, but I'm not sure it's quite right, even though the GETs are working.
context.Routes.MapHttpRoute(
name: "V1/EmploymentApi",
routeTemplate: "api/v1/Employment/{action}/{jobId}",
defaults: new { controller = "Employment", jobId = RouteParameter.Optional, action = "Get" }
);
Case #1 seems to be conflicting due to the framework expecting an action rather than the 221. I'd like to be able to get all of these cases working.
You may want to look at Attribute Routing (Web API 1 and Web API 2).
public class StudentsController : ApiController
{
[HttpPut]
[Route("students/{studentId}/employment")]
public void UpdateStudentEmployment(int studentId) { ... }
[HttpPut]
[Route("students/{studentId}/employment/jobs/{jobId}")]
public void UpdateStudentEmploymentJob(int studentId, int jobId) { ... }
}

Web API 5.0.0-rc1 breaking default routes

Before I updated from Web API 5.0.0-beta2 to 5.0.0-rc1 I could do something like this:
[RoutePrefix("api/v1/test")]
public class TestController : ApiController
{
[HttpGet]
public TestString Get()
{
return new TestString { str = "HELLO WORLD" };
}
}
so when I went to the URL /api/v1/test it would land on the Get() function.
After updating to Web API 5.0.0-rc1 I get a 404 when going to /api/v1/test
However, this works:
[RoutePrefix("api/v1")]
public class TestController : ApiController
{
[HttpGet("test")]
public TestString Get()
{
return new TestString { str = "HELLO WORLD" };
}
}
Can you explain why this doesn't work any longer?
** EDIT **
[HttpGet("")] works. Then it breaks on that Get() function.
I am not sure but I believe the Http[Get, Post, etc] type attributes have had their routing properties removed. This link hints at it:
http://blogs.microsoft.co.il/blogs/bnaya/archive/2013/08/28/asp-net-web-api-attribute-based-routing.aspx
be aware that most of the Attribute based Routing samples available
today on the web, is using old attribute like [PUT] or [HttpPut] which
is no longer supported on the latest bit (currently available from the
ASP.NET nightly build, http://www.myget.org/F/aspnetwebstacknightly/
), those attributes ware replaced with [Route] attribute.
Please see https://aspnetwebstack.codeplex.com/SourceControl/list/changesets and https://aspnetwebstack.codeplex.com/workitem/1206. Basically, the goal is to separate verb filters from attribute routing.

How to send an array via a URI using Attribute Routing in Web API?

I'm following the article on Attribute Routing in Web API 2 to try to send an array via URI:
[HttpPost("api/set/copy/{ids}")]
public HttpResponseMessage CopySet([FromUri]int[] ids)
This was working when using convention-based routing:
http://localhost:24144/api/set/copy/?ids=1&ids=2&ids=3
But with attribute routing it is no longer working - I get 404 not found.
If I try this:
http://localhost:24144/api/set/copy/1
Then it works - I get an array with one element.
How do I use attribute routing in this manner?
The behavior you are noticing is more related to Action selection & Model binding rather than Attribute Routing.
If you are expecting 'ids' to come from query string, then modify your route template like below(because the way you have defined it makes 'ids' mandatory in the uri path):
[HttpPost("api/set/copy")]
Looking at your second question, are you looking to accept a list of ids within the uri itself, like api/set/copy/[1,2,3]? if yes, I do not think web api has in-built support for this kind of model binding.
You could implement a custom parameter binding like below to achieve it though(I am guessing there are other better ways to achieve this like via modelbinders and value providers, but i am not much aware of them...so you could probably need to explore those options too):
[HttpPost("api/set/copy/{ids}")]
public HttpResponseMessage CopySet([CustomParamBinding]int[] ids)
Example:
[AttributeUsage(AttributeTargets.Parameter, Inherited = false, AllowMultiple = false)]
public class CustomParamBindingAttribute : ParameterBindingAttribute
{
public override HttpParameterBinding GetBinding(HttpParameterDescriptor paramDesc)
{
return new CustomParamBinding(paramDesc);
}
}
public class CustomParamBinding : HttpParameterBinding
{
public CustomParamBinding(HttpParameterDescriptor paramDesc) : base(paramDesc) { }
public override bool WillReadBody
{
get
{
return false;
}
}
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext,
CancellationToken cancellationToken)
{
//TODO: VALIDATION & ERROR CHECKS
string idsAsString = actionContext.Request.GetRouteData().Values["ids"].ToString();
idsAsString = idsAsString.Trim('[', ']');
IEnumerable<string> ids = idsAsString.Split(',');
ids = ids.Where(str => !string.IsNullOrEmpty(str));
IEnumerable<int> idList = ids.Select(strId =>
{
if (string.IsNullOrEmpty(strId)) return -1;
return Convert.ToInt32(strId);
}).ToArray();
SetValue(actionContext, idList);
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
tcs.SetResult(null);
return tcs.Task;
}
}

Sitecore context not loaded in custom controller

I followed this tutorial, and created this code:
using Glass.Sitecore.Mapper;
using Sitecore.Mvc.Controllers;
using Sitecore.SecurityModel;
using SitecoreCMSMVCBase.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace SitecoreCMSMVCBase.Controllers
{
public class CommentController : SitecoreController
{
ISitecoreContext _context;
ISitecoreService _master;
public CommentController()
: this(
new SitecoreContext(),
new SitecoreService("master"))
{
}
/// <summary>
/// This constructor can be used with dependency injection or unit testing
/// </summary>
public CommentController(ISitecoreContext context, ISitecoreService master)
{
_context = context;
_master = master;
}
[HttpGet]
public override ActionResult Index()
{
var model = _context.GetCurrentItem<CommentPage>();
return View(model);
}
[HttpPost]
public ActionResult Index(Comment comment)
{
var webModel = _context.GetCurrentItem<CommentPage>();
if (ModelState.IsValid)
{
var masterModel = _master.GetItem<CommentPage>(webModel.Id);
if (masterModel.CommentFolder == null)
{
CommentFolder folder = new CommentFolder();
folder.Name = "Comments";
using (new SecurityDisabler())
{
_context.Create(masterModel, folder);
}
masterModel.CommentFolder = folder;
}
using (new SecurityDisabler())
{
comment.Name = DateTime.Now.ToString("yyyyMMddhhmmss");
//create the comment in the master database
_master.Create(masterModel.CommentFolder, comment);
webModel.CommentAdded = true;
}
}
return View(webModel);
}
}
}
Models are identical with tutorial, so I will not paste them.
My route configuration looks like this:
routes.MapRoute(
"CommentController", // Route name
"Comment/{action}/{id}", // URL with parameters
new { controller = "Comment", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
When I navigate to /comment I see this exception:
Glass.Sitecore.Mapper.MapperException: Context has not been loaded
I tried with commenting my route specification (as there was nothing about routes in tutorial), and then error is different (throwing by Sitecore CMS itself):
The requested document was not found
Do you know how to load Sitecore context into custom Controller, and make this simple example work? I was looking everywhere but couldn't find any good answer...
I think this is more a Glass setup issue, rather than an MVC routing problem.
To setup Glass, you need to initialise the context in your application start method in your Global.asax file.
var loader = new Glass.Sitecore.Mapper.Configuration.Attributes.AttributeConfigurationLoader(
"Glass.Sitecore.Mapper.Tutorial.Models, Glass.Sitecore.Mapper.Tutorial");
Glass.Sitecore.Mapper.Context context = new Context(loader);
For other Glass-setup related stuff I recommend following the first tutorial on the glass.lu website.
http://www.glass.lu/tutorials/glass-sitecore-mapper-tutorials/tutorial-1-setup/
This method doesn't need Glass at all!
First step is to set your route in Global.asax file.
routes.MapRoute(
"DemoController", // Route name
"Demo/{action}/{param}", // URL with parameters
new { controller = "Demo", action = "Index", param = "", scItemPath = "/sitecore/content/DemoHomePage" } // Parameter defaults
);
Notice that controller is not taken as parameter, but is fixed, to prevent handling it by Sitecore. More info here and here. Notice that there is one additional parameter - scItemPath. It contains path to item which by default will be included in page context.
Having this route our traffic from /demo is handled by DemoController and Index action. Inside this action all you need is to add is this line:
Sitecore.Data.Items.Item item = Sitecore.Mvc.Presentation.PageContext.Current.Item;
item variable will contain your Sitecore item pointed by scItemPath.
And that's all - it should work well now - hope it helps!

WebApi in MVC3, cannot hit my API Controller (500 internal error)

I have the below route mapped in my AreaRegistration:
public override void RegisterArea(AreaRegistrationContext context)
{
if (context != null)
{
context.Routes.MapHttpRoute(
name: "API_Default",
routeTemplate: "Areas/Test/AIO/api/{controller}/{id}",
defaults: new
{
id = RouteParameter.Optional
});
When I look at the Global.asax file, I can see the HttpRoute is being Registered, and is listed in the RouteTable.Routes as a {System.Web.Http.WebHost.Routing.HttpWebRoute}.
Problem is, when I go to the url... https://myRoot/Areas/Test/AIO/api/AioApi/test or https://myRoot/Areas/Test/AIO/api/AioApi, it's giving me a 500 internal server error.
I'm not sure how to view the actual error, when stepping thru the code I cannot see anything after it leaves Application_BeginRequest.
My controller code:
public class AioApiController : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Any insight as to why I cannot hit the API controller? I can hit my regular MVC controller in the same context.
Any help is greatly appreciated!
One thing I found out with WebApi is that there are two Route collections: System.Web.Routing.RouteCollection and System.Web.Http.HttpRouteCollection. I believe (but can't remember) that you need to use the latter in order for your ApiController derivations to work properly (luckily the syntax is the same).

Resources