.net MVC Odata endpoint works on POST, but 404s on GET - asp.net-mvc

My question should have a simple answer, but after hours of experimentation and googling, I have nothing so now I'm here.
I am working with .net MVC 5 odata 2.0. I am trying to create a new odata endpoint that works on a get. I've successfully created a few endpoints that work with POST, but I can't seem to get one that works as a GET.
relevant code
WebApiConfig.cs
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Review>("Reviews");
builder.EntitySet<Strategy>("Strategies");
ActionConfiguration ReviewsInStrategy = builder.Entity<Strategy>().Action("ReviewsInStrategy");
ReviewsInStrategy.ReturnsCollectionFromEntitySet<Review>("Reviews");
config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
StrategiesController.cs
[EnableQuery]
public IQueryable<Review> ReviewsInStrategy([FromODataUri] Guid key){
Strategy strategy = db.Strategies.Find(key);
return strategy.Reviews
}
Now I go to fiddler and try a GET to
[myurl]/odata/Strategies(guid'[myguid]')/ReviewsInStrategy
I get a 404 result. But when I change fiddler to a POST (no other change - I don't add accept headers or content types or anything) it works just fine.
How do I make this work with GET?

You need to add a Get-function to your controller:
[EnableQuery]
public SingleResult<Strategies> Get([FromODataUri] Guid key)
{
IQueryable<Strategies> result = db.Strategies.Where(p => p.Id == key);
return SingleResult.Create(result);
}
That way you can use a GET request on the Strategies-resource. If you still want to use a custom action you must add the parameter to your config:
ActionConfiguration ReviewsInStrategy = builder.Entity<Strategy>().Action("ReviewsInStrategy").Returns<Review>().Parameter<Guid>("Key");
[HttpGet]
[ODataRoute("ReviewsInStrategy(Key={key})")]
public IHttpActionResult<Review> ReviewsInStrategy([FromODataUri] Guid key){
Strategy strategy = db.Strategies.Find(key);
return Ok(strategy.Reviews);
}
Not sure if all this works with the complex type Guid. Maybe you need to change it to string and parse it inside the function. Here is a good msdn article about this.

Related

Passing Authentication Token with Breeze query

My server side breeze api calls require me to validate the token value provided with each call before returning any data. To achieve this, i am passing TokenId with each Entity Query using withParameters function of breeze Entity Query and specify the parameter on my server side controller action as illustrated below.
Following is how i am doing it right now:
Client Side
function GetCustomers(){
return breeze.EntityQuery.from('Customers')
.withParameters({ TokenId: 'token value' })
.using(entityManager).execute()
}
Server Side
[HttpGet]
public IQueryable<Customer> Customers(string TokenId)
{
//server side logic
}
This for some reason looks to me can be simplified using some configuration on Breeze Entity Manager which automatically adds the parameter value to every query sent from client. And also, on server side, i don't think its necessary to have tokenId parameter defined on each controller method. This should be easy to solve with Action Filters on the server side.
Can someone point me in right direction if it is possible and how to do this?
Thanks
You should always pass custom authentication information, such as tokens, in the headers.
If you can pass it as a custom header you can search for 'define custom headers breeze.js' to see how that can be done.
A good example -
http://breeze.github.io/doc-js/server-ajaxadapter.html
Also keep in mind that if you put the token in the query string that is persisted by most of the servers that your call is crossing over, making it much easier to find tokens for your application if an evil-doer were to download the logs from that server.
Also keep in mind that if you are using cross-domain requests (CORS) you will need to enable the custom header on the server side that is receiving the call.
To give an example of the answer above:
// get the current default Breeze AJAX adapter
var ajaxAdapter: any = breeze.config.getAdapterInstance('ajax');
// set fixed headers
ajaxAdapter.defaultSettings = {
headers: {
"Bearer": this.bearerToken
}
};
Note also that if you are using ASP.net Identity (which includes a cookie based authentication), you must include this code so that the asp.net pipeline does NOT use that cookie:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new Ask.Filters.RequireHttpsAttribute());
//This forces http header authentication which is required for web api calls.
config.SuppressDefaultHostAuthentication();
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Note also that you should use only https, with this filter:
public class RequireHttpsAttribute : AuthorizationFilterAttribute
{
public int Port { get; set; }
public RequireHttpsAttribute()
{
Port = 443;
}
public override void OnAuthorization(HttpActionContext actionContext)
{
var request = actionContext.Request;
if (request.RequestUri.Scheme != Uri.UriSchemeHttps)
{
var response = new HttpResponseMessage();
if (request.Method == HttpMethod.Get || request.Method == HttpMethod.Head)
{
var uri = new UriBuilder(request.RequestUri);
uri.Scheme = Uri.UriSchemeHttps;
uri.Port = this.Port;
response.StatusCode = HttpStatusCode.Found;
response.Headers.Location = uri.Uri;
}
else
{
response.StatusCode = HttpStatusCode.Forbidden;
}
actionContext.Response = response;
}
else
{
base.OnAuthorization(actionContext);
}
}
}
And finally, make sure you use the [Authorize] and [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)] attribute on your controllers.
See the Full Sample and article on asp.net
I suggest using oAuth tokens in the header. I am currently using Auth0, but there are many others including Google Firebase. BreezeJS has been expanded such that it will accept AuthHttp as it's HTTP client for all calls. AuthHttp will automatically add a bearer token to the header of every call to the server.
One the server side it's a matter of adding the proper oAuth client lib (via Nuget) which automatically parses out the bearer token, validates it against the oAuth server, and makes claims available for use within your API call.
For example, in my implementation my api code will receive a claim that contains the users id, which I can then validate against my db and use to filter all actions. This keeps user B from loading user A's data by directly calling the api in a browser.
For example, all API calls are first validated by the oAuth subsystem. Once my api code is executed I know the caller has been validated and I use the passed claims (user id) to access only the calling users data - REGARDLESS what was actually requested by the API call parameters (which can be forged as easily as editing a URL string).
A simple example of this would be exposing your UserSet in the API. If you don't filter by the user claim id you must realize that every user in your system will be accessible by constructing a simple URL in a browser.
Here I filter out and return only the user record identified by the user id in the auth claim. Note I use #if AUTH simply for testing as PROD will always have AUTH turned on.
#if AUTH
[Authorize]
#endif
[HttpGet]
[EnableBreezeQuery(MaxExpansionDepth = 5)]
public IQueryable<User> UserSet()
{
#if AUTH
Guid guid = userGuid();
return _efContext.Context.UserSet.Where(x => x.active && x.guid == guid);
#else
return _efContext.Context.UserSet;
#endif
}
All of this sounds complicated, but it really is quite easy to implement.
Thanks,
Mike

How can I return IQueyrable DTO from Webapi Get so I can use Odata filters

I'm trying to use a Odata filters with ODP.net with Entity framework inside of web api project ASP.NET MVC 4.0 RC. I want to return an IQueryable of OwnDTO . I get an internal 500 error without any details. I know there is an error generation bug with webapi RC, but I dont think that bug is my issue.
Get http://localhost:51744/api/Owner called using Fiddler
[Queryable]
public IQueryable<OwnDTO> Get()
{
using (Entities context = new Entities())
{
var query = from item in context.Owners
select
new OwnDTO
{
Name = item.Name
};
return query.AsQueryable();
}
}
//very simple for example
public class OwnDTO
{
public string Name;
}
I do not want to have use my Oracle EF generated classes (DAO) to return from my Get, but I know I can if I replace EntityObject with a more friendly interface. If I return IEnumerable it works, but I want Odata filters.
Update incase someone wants a working example.. Automapper or simliar should be used in the linq and the context should injected.
[Queryable]
public IQueryable<OwnDTO> Get()
{
{
var query = from item in Hack._EFContext.Owners
select
new OwnDTO
{
Name = item.Name
};
return query.AsQueryable();
}
}
That works fine, but it looks like Odata is removed post RC. So I need to search down another path.
It does work in RC but perhaps not in RTM when it ships - not quite clear yet.
Your problem is that you are disposing your context since you are using a using block. So context get disposed before the data is retrieved.
So instead of using register your object for disposal at the end of request. Tugberk has a post here.

Using the Json.NET serializer in an MVC4 project

I'm starting to learn Json.NET, but I'm having trouble using its serializer. I have a new MVC4 project with a Web.API service:
public class PTE_TestsController : ApiController {
PTE_TestsRepository _repository = new PTE_TestsRepository();
// GET /api/PTE_Tests/5
public HttpResponseMessage<string> Get(int id) {
try {
PTE_Test test = _repository.GetTest(id);
return new HttpResponseMessage<string>(JsonConvert.SerializeObject(test));
} catch {
return new HttpResponseMessage<string>(HttpStatusCode.NotFound);
}
}
}
JsonConvert.SerializeObject() works as expected and returns a string. My Web.API controller returns that as part of an HttpResponseMessage. The end result, when viewed in Fiddler, is not JSON data, but JSON data being serialized again (I think):
"{\"ID\":1,\"Name\":\"Talar Tilt\",\"TagID\":1,\"PracticeID\":1,
\"SpecificAreaID\":1,\"TypeID\":1,\"VideoID\":1,\"PicID\":1}"
Does someone know how to turn off the default serializer so I can use Json.NET directly? By the way, I'm not using the default serializer because I can't figure out how to make it work with complex objects (PTE_Test will eventually contain members of type List).
Alternately, it will also solve my problem if someone can explain how to use the default serializer with complex objects. MSDN's explanation didn't help me.
Rick Strahl has a blog on that here with a code that works.
As others have pointed out, you need to create a formatter and replace the DataContractSerializer with the JSON.NET serializer. Although, if you're not in a rush for JSON.NET specifically, rumor has it that next beta/rc is going to have support for JSON.NET built in.
Conceptually, however, you're missing part of the magic of WebAPI. With WebAPI you return your object in it's native state (or IQueryable if you want OData support). After your function call finishes the Formatter's take over and convert it into the proper shape based on the client request.
So in your original code, you converted PTE_Test into a JSON string and returned it, at which point the JSON Formatter kicked in and serialized the string. I modified your code as follows:
public class PTE_TestsController : ApiController {
PTE_TestsRepository _repository = new PTE_TestsRepository();
public HttpResponseMessage<PTE_Test> Get(int id) {
try {
PTE_Test test = _repository.GetTest(id);
return new HttpResponseMessage(test);
} catch {
return new HttpResponseMessage<string>(HttpStatusCode.NotFound);
}
}
}
Notice how your function returns PTE_Test instead of string. Assuming the request came in with a request header of Accept = application/json then the JSON formatter will be invoked. If the request had a header of : Accept = text/xml the XML formatter is invoked.
There's a decent article on the topic here. If you're a visual learner, Scott Gu shows some examples using fiddler in this video, starting around 37 minutes. Pedro Reys digs a little deeper into content negotiation here.
The way to do this is to use formatters.
Check out: https://github.com/WebApiContrib/WebAPIContrib/tree/master/src/WebApiContrib.Formatting.JsonNet.
Json.NET support will be in the RC release of Web API.

ASP.NET MVC QueryString defaults overriding supplied values?

Using ASP.NET MVC Preview 5 (though this has also been tried with the Beta), it appears that querystring defaults in a route override the value that is passed in on the query string. A repro is to write a controller like this:
public class TestController : Controller
{
public ActionResult Foo(int x)
{
Trace.WriteLine(x);
Trace.WriteLine(this.HttpContext.Request.QueryString["x"]);
return new EmptyResult();
}
}
With route mapped as follows:
routes.MapRoute(
"test",
"Test/Foo",
new { controller = "Test", action = "Foo", x = 1 });
And then invoke it with this relative URI:
/Test/Foo?x=5
The trace output I see is:
1
5
So in other words the default value that was set up for the route is always passed into the method, irrespective of whether it was actually supplied on the querystring. Note that if the default for the querystring is removed, i.e. the route is mapped as follows:
routes.MapRoute(
"test",
"Test/Foo",
new { controller = "Test", action = "Foo" });
Then the controller behaves as expected and the value is passed in as the parameter value, giving the trace output:
5
5
This looks to me like a bug, but I would find it very surprising that a bug like this could still be in the beta release of the ASP.NET MVC framework, as querystrings with defaults aren't exactly an esoteric or edge-case feature, so it's almost certainly my fault. Any ideas what I'm doing wrong?
The best way to look at ASP.NET MVC with QueryStrings is to think of them as values that the route does not know about. As you found out, the QueryString is not part of the RouteData, therefore, you should keep what you are passing as a query string separate from the route values.
A way to work around them is to create default values yourself in the action if the values passed from the QueryString are null.
In your example, the route knows about x, therefore your url should really look like this:
/Test/Foo or /Test/Foo/5
and the route should look like this:
routes.MapRoute("test", "Test/Foo/{x}", new {controller = "Test", action = "Foo", x = 1});
To get the behavior you were looking for.
If you want to pass a QueryString value, say like a page number then you would do this:
/Test/Foo/5?page=1
And your action should change like this:
public ActionResult Foo(int x, int? page)
{
Trace.WriteLine(x);
Trace.WriteLine(page.HasValue ? page.Value : 1);
return new EmptyResult();
}
Now the test:
Url: /Test/Foo
Trace:
1
1
Url: /Test/Foo/5
Trace:
5
1
Url: /Test/Foo/5?page=2
Trace:
5
2
Url: /Test/Foo?page=2
Trace:
1
2
Hope this helps clarify some things.
One of my colleagues found a link which indicates that this is by design and it appears the author of that article raised an issue with the MVC team saying this was a change from earlier releases. The response from them was below (for "page" you can read "x" to have it relate to the question above):
This is by design. Routing does not
concern itself with query string
values; it concerns itself only with
values from RouteData. You should
instead remove the entry for "page"
from the Defaults dictionary, and in
either the action method itself or in
a filter set the default value for
"page" if it has not already been set.
We hope to in the future have an
easier way to mark a parameter as
explicitly coming from RouteData, the
query string, or a form. Until that is
implemented the above solution should
work. Please let us know if it
doesn't!
So it appears that this behaviour is 'correct', however it is so orthogonal to the principle of least astonishment that I still can't quite believe it.
Edit #1: Note that the post details a method of how to provide default values, however this no longer works as the ActionMethod property he uses to access the MethodInfo has been removed in the latest version of ASP.NET MVC. I'm currently working on an alternative and will post it when done.
Edit #2: I've updated the idea in the linked post to work with the Preview 5 release of ASP.NET MVC and I believe it should also work with the Beta release though I can't guarantee it as we haven't moved to that release yet. It's so simple that I've just posted it inline here.
First there's the default attribute (we can't use the existing .NET DefaultValueAttribute as it needs to inherit from CustomModelBinderAttribute):
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class DefaultAttribute : CustomModelBinderAttribute
{
private readonly object value;
public DefaultAttribute(object value)
{
this.value = value;
}
public DefaultAttribute(string value, Type conversionType)
{
this.value = Convert.ChangeType(value, conversionType);
}
public override IModelBinder GetBinder()
{
return new DefaultValueModelBinder(this.value);
}
}
The the custom binder:
public sealed class DefaultValueModelBinder : IModelBinder
{
private readonly object value;
public DefaultValueModelBinder(object value)
{
this.value = value;
}
public ModelBinderResult BindModel(ModelBindingContext bindingContext)
{
var request = bindingContext.HttpContext.Request;
var queryValue = request .QueryString[bindingContext.ModelName];
return string.IsNullOrEmpty(queryValue)
? new ModelBinderResult(this.value)
: new DefaultModelBinder().BindModel(bindingContext);
}
}
And then you can simply apply it to the method parameters that come in on the querystring, e.g.
public ActionResult Foo([Default(1)] int x)
{
// implementation
}
Works like a charm!
I think the reason querystring parameters do not override the defaults is to stop people hacking the url.
Someone could use a url whose querystring included controller, action or other defaults you didn't want them to change.
I've dealt with this problem by doing what #Dale-Ragan suggested and dealing with it in the action method. Works for me.
I thought the point with Routing in MVC is to get rid of querystrings. Like this:
routes.MapRoute(
"test",
"Test/Foo/{x}",
new { controller = "Test", action = "Foo", x = 1 });

Is it possible to implement X-HTTP-Method-Override in ASP.NET MVC?

I'm implementing a prototype of a RESTful API using ASP.NET MVC and apart from the odd bug here and there I've achieve all the requirements I set out at the start, apart from callers being able to use the X-HTTP-Method-Override custom header to override the HTTP method.
What I'd like is that the following request...
GET /someresource/123 HTTP/1.1
X-HTTP-Method-Override: DELETE
...would be dispatched to my controller method that implements the DELETE functionality rather than the GET functionality for that action (assuming that there are multiple methods implementing the action, and that they are marked with different [AcceptVerbs] attributes). So, given the following two methods, I would like the above request to be dispatched to the second one:
[ActionName("someresource")]
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult GetSomeResource(int id) { /* ... */ }
[ActionName("someresource")]
[AcceptVerbs(HttpVerbs.Delete)]
public ActionResult DeleteSomeResource(int id) { /* ... */ }
Does anybody know if this is possible? And how much work would it be to do so...?
You won't be able to use the [AcceptVerbs] attribute as-is since it's tied to the request's actual HTTP verb. Fortunately the [AcceptVerbs] attribute is very simple; you can see the source for yourself at http://www.codeplex.com/aspnet/SourceControl/changeset/view/21528#266431.
In short, subclass AcceptsVerbsAttribute and override the IsValidForRequest() method. The implementation would be something like the following:
string incomingVerb = controllerContext.HttpContext.Request.Headers["X-HTTP-Method-Override"] ?? controllerContext.HttpContext.Request.Method;
return Verbs.Contains(incomingVerb, StringComparer.OrdinalIgnoreCase);
Levi's answer is great. Additionally, I added a check in the custom AcceptsVerbsAttribute that also examines the FORM collection, so you can simply put a hidden input to trigger the DELETE (similar to MVC 2's Html.HttpMethodOverride(HttpVerbs.Delete)).
<input name="X-HTTP-Method-Override" type="hidden" value="DELETE" />
Change the incomingVerb assignment to:
string incomingVerb = controllerContext.HttpContext.Request.Headers["X-HTTP-Method-Override"] ?? controllerContext.HttpContext.Request.Form["X-HTTP-Method-Override"] ??controllerContext.HttpContext.Request.HttpMethod;
Be careful with this approach! See a related post by Stephen Walther.
Hopefully this helps someone.
Insert to Form:
<%= Html.HttpMethodOverride(HttpVerbs.Delete) %>
This conversation is a bit old, but I wanted to share what I have found using mvc 2:
Browsers support two HTTP verbs: GET and POST, but ASP.NET MVC 2 allows you to simulate Put, Get, and Delete using Html.HttpMethodOverride helper method. Internally, this works by sending the verb in an X-HTTP-Method-Override form field. The behavior of HttpMethodOverride is used by the [AcceptVerbs] attribute as well as the new shorter verb attributes:
For example, the action declaration:
[ActionName("someresource")]
[HttpDelete]
public ActionResult DeleteSomeResource()
should take responsibility for your get request that has the X-HTTP-Method-Override set to Delete.
I'm surprised that this hasn't been mentioned yet, but ASP.NET MVC natively supports X-HTTP-Method-Override and has been doing so from at least version 2. There's no need to write custom code to handle this.
It work in the following way:
Inside AcceptVerbsAttribute (also proxied by [HttpPut], [HttpPost], etc), there's an IsValidForRequest method. Inside that method, it checks with Request.GetHttpMethodOverride(), which returns the proper overriden HTTP method with the following conditions:
Overriding is only supported in POST requests. All others are ignored.
If the X-HTTP-Method-Override value is GET or POST, it's ignored. This makes sense, as you'd never need to override with these values.
It looks for X-HTTP-Method-Override in the following places in this priority:
1) HTTP Header
2) Form Body
3) Query String
If you're really curious, here's how GetHttpMethodOverride() looks (from MVC 3's source code):
public static class HttpRequestExtensions {
internal const string XHttpMethodOverrideKey = "X-HTTP-Method-Override";
public static string GetHttpMethodOverride(this HttpRequestBase request) {
if (request == null) {
throw new ArgumentNullException("request");
}
string incomingVerb = request.HttpMethod;
if (!String.Equals(incomingVerb, "POST", StringComparison.OrdinalIgnoreCase)) {
return incomingVerb;
}
string verbOverride = null;
string headerOverrideValue = request.Headers[XHttpMethodOverrideKey];
if (!String.IsNullOrEmpty(headerOverrideValue)) {
verbOverride = headerOverrideValue;
}
else {
string formOverrideValue = request.Form[XHttpMethodOverrideKey];
if (!String.IsNullOrEmpty(formOverrideValue)) {
verbOverride = formOverrideValue;
}
else {
string queryStringOverrideValue = request.QueryString[XHttpMethodOverrideKey];
if (!String.IsNullOrEmpty(queryStringOverrideValue)) {
verbOverride = queryStringOverrideValue;
}
}
}
if (verbOverride != null) {
if (!String.Equals(verbOverride, "GET", StringComparison.OrdinalIgnoreCase) &&
!String.Equals(verbOverride, "POST", StringComparison.OrdinalIgnoreCase)) {
incomingVerb = verbOverride;
}
}
return incomingVerb;
}
}
Have you looked at Simply Restful Routing? It already does this.
Edited Feb 2010 to add: Method overrides are built into MVC 2.
The X-HTTP-Method-Override is a custom header and most likely isn't supported by your web container.
Are you calling this from a web page? If so, you should probably use XmlHttpRequest with DELETE (or whatever verb you want). Better yet, use a JS framework to do the heavy lifting for you.
You could create an ActionFilter that implements OnActionExecuting, which fires before the controller action is invoked. You could then interrogate the request headers, and redirect based on the value of the X-HTTP-Method-Override header, when present.

Resources