How do I expose a navigation property over OData 4 and WebApi 2.2? - asp.net-web-api

I have a navigation property on a model, Site.Locality and although its foreign key is serialized and available to consumers (Site.LocalityName) I'd like the locality itself to be available from:
~/Site('A')/Locality
How is this done in OData v4 over WebApi 2.2?

On your controller for the Site entity, add the following action:
// Implies that the controller has [ODataRoutePrefix("Sites")]
[ODataRoute("({name})/Locality")]
public async Task<Locality> GetLocality([FromODataUri] string name)
{
// Add try-catch or null 404 handling.
var site = await this.Repository.GetAsync(new[] { name });
return site.Locality;
}
Obviously, place your own DAL code in there, this is just an example.
It's very clear to see that this is achieved through nothing more complex than a simple route and action on your controller.
That said, there is some mapping happening under the hood. For example, you couldn't just expose any arbitrary navigation property:
[ODataRoute("({name})/Wangachop")]
public string GetWangachop([FromODataUri] string name)
{
return "Wangaaa!";
}
Would yield:
The path template 'Sites({name})/Wangachop' on the action 'GetWangachop' in controller 'Sites' is not a valid OData path template. Found an unresolved path segment 'Wangachop' in the OData path template 'Sites({name})/Wangachop'.

Related

How to map different HTTP methods on the same url to different controllers?

I have my API for a small part of my application split over two controllers, because of (external) requirements on the casing of JSON data in the API (some requests should use camelCasing, while others should use PascalCasing).
Now, I have a url that I want to map with PascalCasing for GET, but camelCasing for PUT, so I tried the following:
[PascalCasing] // custom attribute, part of our code
// We configure all controllers that *don't* have this to use
// camelCasing
public class PascalCasedController : ApiController
{
[HttpGet]
[Route("url/to/my/resource/{id}")]
public IHttpActionResult(int id)
{
return Ok(GetResource(id));
}
}
public class CamelCasedController : ApiController
{
[HttpPut]
[Route("url/to/my/resource/{id}")]
public IHttpActionResult(int id, Resource resource)
{
SaveResource(id, resource);
return Ok();
}
}
The GET request works as expected, but if I try to PUT something there with Fiddler, I get the following error message:
Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.
The request has found the following matching controller types:
MyProject.PascalCaseController
MyProject.CamelCaseController
I realize this is probably because WebAPI maps routes to controllers first and actions next, but if HTTP methods are considered, there really isn't any ambiguity here. Is there any way that I can tell WebAPI how to do this, without having to have the methods in the same controller?
#Tomas - There's an interface "System.Web.Http.Dispatcher.IHttpControllerSelector" exposed in System.Web.Http assembly. You can use that interface and create your own HttpControllerSelector. You can then replace the DefaultControllerSelector with your custom controller selector in the HttpConfiguration during AreaRegistration.
httpConfig.Services.Replace(typeof(IHttpControllerSelector), new CustomControllerSelector(services.GetHttpControllerSelector()));
In this custom controller selector you can write your own implementation of SelectController() method of IHttpControllerSelector in which you can call GetControllerMapping() method of IHttpControllerSelector. This will give you the list of all the controllers registered. For every controller you can check for the DeclaredMethods and check for the CustomAttributes for each of the DeclaredMethods. In your case it will be either HttpGetAttribute or HttpPutAttribute.
Check the Method type of the incoming HttpRequestMessage (GET/PUT) and compare it against the value of the CustomAttributes. If you find a match of the combination of incoming request URL and the the respective Http Verb then you take that HttpControllerDiscriptor and return it from the SelectController() method..
This will allow you to have same URL with different methods in two different controllers.

Controller not filtering data in Breeze query in DotNetNuke Module

I am trying to include the basic Breeze sample in a DotNetNuke module (it works fine in a standalone WebAPI project). To simplify things I remove the client and will just refer to the URL JSON calls I make in the Chrome browser.
I can see my metadata and a full list of items, eg:
http://www.dnndev.me/DesktopModules/framework/api/breeze/dare/metadata
http://www.dnndev.me/DesktopModules/framework/api/breeze/dare/todos
however, when I try to filter the list from the URL, it always returns the full list, e.g.
http://www.dnndev.me/DesktopModules/framework/api/breeze/dare/todos?=DareId%20eq%204
I think it is something to do with the way I have declared the MapHTTRoute. The problem is that DotNetNuke modules do not have a Global.ascx. I have copied the BreezeWebApiconfig.cs file into my App_Start folder and this does fire when I debug, however DotNetNuke uses mechanism for registering routes:
using DotNetNuke.Web.Api;
namespace SmartThinker.Modules.Framework
{
public class RouteMapper : IServiceRouteMapper
{
public void RegisterRoutes(IMapRoute mapRouteManager)
{
mapRouteManager.MapHttpRoute("framework", "BreezeApi", "breeze/{controller}/{action}", new[] { "SmartThinker.Modules.Framework.Controllers" });
}
}
}
I have read up on http://www.breezejs.com/documentation/web-api-controller#note01 and http://www.breezejs.com/documentation/web-api-routing but it seems that it's something to do with the way DNN registers the routes. Is there anyway to do this without using BreezeWebApiConfig.cs?
My controller code has the BreezeController attribute. (When I do connect the sample client to it I do get a list of items - it just does not filter, so I think it is something to with the OData Action filters. How can I debug where the problem is?
Update 1)
Here is the metadata:
http://www.ftter.com/desktopmodules/framework/api/dare/metadata
The GetUsers method:
http://www.ftter.com/desktopmodules/framework/api/dare/getusers
and the GetUsers method trying to filter by UserID (which doesn't work, which is the issue)
http://www.ftter.com/desktopmodules/framework/api/dare/getusers?=UserID%20eq%204
http://www.ftter.com/desktopmodules/framework/api/dare/GetUsersWithoutCors?=UserID%20eq%204 (this returns IQueryable)
Here is the controller:
[BreezeController]
public class DareController : DnnApiController
{
private readonly EFContextProvider<FrameworkContext> contextProvider = new EFContextProvider<FrameworkContext>();
[AllowAnonymous]
[HttpGet]
public HttpResponseMessage Metadata()
{
var response = Request.CreateResponse(HttpStatusCode.OK, contextProvider.Metadata());
return GetResponseWithCorsHeader(response);
}
[AllowAnonymous]
[HttpGet]
public HttpResponseMessage GetUsers()
{
var userInfoController = new UserInfoController();
var response = Request.CreateResponse(HttpStatusCode.OK, userInfoController.GetUsers());
return GetResponseWithCorsHeader(response);
}
[AllowAnonymous]
[HttpGet]
public IQueryable<User> GetUsersWithoutCors()
{
return contextProvider.Context.Users;
}
}
The routing is not really a Breeze issue. How your server routes requests to your controller is up to you. What we do out-of-the-box is just one way among innumerable many.
You have the [BreezeController] attribute on your controller yes? Can you put a sample endpoint up where we could hit it. Might get some clues from that. Also post the controller. A tiny example should do ... something returning metadata and one method returning IQueryable.
Update 25 Jun 2013
I think you've discovered a bug in the way our [BreezeController] discovers methods returning IQueryable<T>.
The [BreezeController] attribute scans your Web API controller methods and (in effect) applies the [BreezeQueryable] attribute to methods returning IQueryable<T>.
[BreezeQueryable] is an extension of the Web API's [Queryable] that adds support for $select, $expand, and nested $orderby ... all missing from the current [Queryable].
I see now that your GetUsers() method returns HttpResponseMessage rather than IQueryable<User>. Let's assume that the userInfoController.GetUsers() method inside your method returns IQueryable<User>. Otherwise, the OData query parameters will not apply and we'll have to take this in a different direction. Moving along ...
I checked with v.1.3.6 of the Breeze.WebApi.dll and it does not detect that the HttpResponseMessage is wrapping IQueryable<T>. Therefore, it does not apply the client's OData query criteria (or any other OData modifiers for that matter). This shortcoming (in my opinion) is a bug. The following should be equivalent implementations:
[HttpGet]
public IQueryable<TodoItem> Todos() {
return _repository.Todos;
}
[HttpGet]
public HttpResponseMessage TodosWrapped()
{
return Request.CreateResponse(HttpStatusCode.OK, _repository.Todos);
}
The second, "wrapped" method does not respect the OData query parameters.
Fortunately, there is a workaround until we get this fixed. Just add the [BreezeQueryable] attribute explicitly ... as in:
[HttpGet]
[BreezeQueryable]
public HttpResponseMessage TodosWrapped()
{
return Request.CreateResponse(HttpStatusCode.OK, _repository.Todos);
}
I confirmed that this approach does work.
Thanks for finding this.
Use OData query syntax
A colleague also noticed that your query URL does not use the OData query syntax. You wrote:
... /todos?=DareId%20eq%204
when it should be
... /todos/?$filter=DareId%20eq%204
Make sure you use ?$filter=

The "DELETE" type of Http request does not work in WebAPI?

I have GET, PUT, POST working in my WebAPI project.
The last one of Http requests I am doing is DELeTE, BUT it does not work.
I have read through many posts in here as well as other websites, none of them. e.g.
WebAPI Controller is not being reached on DELETE command
WebAPI Delete not working - 405 Method Not Allowed
ASP.Net WebAPI Delete verb not working
ASP.NET Web API - PUT & DELETE Verbs Not Allowed - IIS 8
http://social.msdn.microsoft.com/Forums/en-US/windowsazuredevelopment/thread/8906fd7e-a60b-484e-be63-9574b9fca44a/
etc...
Are there any workarounds?
Please help, thanks.
Update:
My back-end code:
[HttpDelete]
public HttpResponseMessage Delete(int divisionID)
{
if (divisionID != default(int))
{
var found = dc.MedicareLocalAccounts.SingleOrDefault(m => m.DivisionID == divisionID);
if (found == null)
{
return new HttpResponseMessage(HttpStatusCode.NotFound);
}
dc.MedicareLocalAccounts.Remove(found);
dc.SaveChanges();
return new HttpResponseMessage(HttpStatusCode.OK);
}
return new HttpResponseMessage(HttpStatusCode.NotFound);
}
Now, if I change the parameter type from int to any classes, let's say Division
Delete(Division d)
{
int divisionID = d.DivisionID;
//....the rest is same
}
In this way, it works.
But I just do not want to input the entire object as a parameter to make the DELETE method work as it is not necessary.
So do you have any other better solutions?
Web API handles simple parameter types (int) differently than complex types (classes). By default, a simple parameter is taken from the request URI, and a complex type is taken from the request body.
In your first example, the parameter name is 'divisionID' -- does this match your route variable? The default Web API route is "api/{controller}/{id}", so the parameter should be named 'id'.
A workaround would be using the AttributeRouting library. This is an extension to WebAPI and can be downloaded from nuget. With the AttributeRouting library you could e.g. implement a function with HttpGet that wil perform the delete
[GET("delete/{id}"]
function DeleteThis(int id)
{
...
}

Creating custom RequestContext in ASP.NET MVC

I'm creating a CMS using ASP.NET MVC, and by design, I've decided that each plugin (add-on) should have a key in the incoming HTTP request. Thus, I have this general route in my host application:
{pluginKey}/{controller}/{action}/{id}
I've created a custom controller factory which implements IControllerFactory and of course, it has a method to create controllers base on the ReqeustContext and controller name. However, I want to create an artificial HttpContext (alongside all other relevant objects like HttpRequest, RequestContext, RouteData, etc.) so that controllers of plugins won't misinterpret these URL segments wrongly. In other words, I want to cut the first part of the incoming URL, and make plugins think that they're processing this URL:
{controller}/{action}/{id}
How can I achieve this?
While you could create a new implementation of all the context classes, it seems like a bit of overkill. Why not use a derived Route Handler that applies the filtering functionality before returning the HttpHandler? Here's an example:
// To avoid conflicts with similarly named controllers, I find it to be good practice
// to create a route constraint with the set of all plugin names. If you don't have
// this function already, you should be able to access it with reflection (one time
// per app lifecycle) or you hard-code them. The point is to have a regex which ensures
// only valid plugins will get selected
string[] pluginNames = GetPluginNames();
string pluginNameRegex = string.Join("|",pluginNames);
Route pluginRoute = new Route (
url: "{pluginKey}/{controller}/{action}/{id}",
defaults: null,
constraints: new RouteValueDictionary(new { pluginKey = pluginNameRegex }),
routeHandler: new PluginRouteHandler()
});
// The custom route handler can modify your route data after receiving the RequestContext
// and then send it to the appropriate location. Here's an example (markdown code/untested)
// Note: You don't have to inherit from MvcRouteHandler (you could just implement IRouteHandler
// but I'm assuming you want Mvc functionality as the fallback)
public class PluginRouteHandler : MvcRouteHandler
{
public PluginRouteHandler(IControllerFactory controllerFactory)
: base(controllerFactory)
{}
protected override IHttpHandler GetHttpHandler(RequestContext requestContext){
if(ValidatePluginRoute(requestContext))
{
// we are going to remove the pluginKey from the RequestContext, It's probably wise
// to go ahead and add it to HttpContext.Items, in case you need the data later
requestContext.HttpContext.Items["pluginKey"] = requestContext.RouteData.Values["pluginKey"];
// now let's get ride of it, so your controller factory will process the
// requestContext as you have described.
requestContext.Values.Remove("pluginKey");
// the route will now be interpreted as described so let the flow go to the MvcRouteHandler's method
}
return base.GetHttpHandler(requestContext);
}
static bool ValidatePluginRoute(RequestContext requestContext){
return requestContext.RouteData.ContainsKey("pluginKey");
}
}

Generating url for a resource in asp.net web api outside of ApiController

Looking for a way to construct or generate a url for a specific resource in asp.net web api. It can be done in the controller since it inherits from ApiController hence you get the UrlHelper.
I am looking to construct resource url out of the context of the ApiController.
Here is what I did:
Requires HttpContext/Request, so might not work in Application_Start.
Only tested in WebApi 1
Only works for routes registered in GlobalConfiguration (but if you have some other one, just pass it in instead)
// given HttpContext context, e.g. HttpContext.Current
var request = new HttpRequestMessage(HttpMethod.Get, context.Request.Url) {
Properties = {
{ HttpPropertyKeys.HttpConfigurationKey, GlobalConfiguration.Configuration },
{ HttpPropertyKeys.HttpRouteDataKey, new HttpRouteData(new HttpRoute()) },
{ "MS_HttpContext", new HttpContextWrapper(context) }
}
};
var urlHelper = new UrlHelper(request);
What about the UrlHelper classes:
System.Web.Http.Routing.UrlHelper;
System.Web.Mvc.UrlHelper
The MVC one has some useful static methods accepting routing information or it can be used as an instance created by passing in a RequestContext (which is available in most MVC filters and various other places). The instance methods should be exactly what you need to generate urls.
The HTTP one accepts a ControllerContext (which is also available in most HTTP filters and various other places).
I'm not sure about the ApiController, as I haven't used it before. This may then be redundant for you, but then again, it may not be. Check out your Global.asax.cs file, specifically the RegisterRoutes function. Initially, you should see the following mapping:
routes.MapRoute ("Default", "{controller}/{action}/{id}", new { controller = "MyController", action = "Index", id = "" });
So by default your application is set up to handle routes in the following format:
{ControllerName}/{ActionName}/{ResourceId}
A controller class set up like the following should enable you to receive requests in that format.
class {ControllerName}Controller : ApiController
{
public ActionResult {ActionName} (string id)
{
// fetch your resource by its unique identifier
}
}

Resources