I am struggling with the OData WebApi way to configure routing - mostly by not being able to get any sensible debugging information.
The API in question is part of a server service (as in: windows service) and as such OWIN based.
I have for example the following function:
function = builder.Function("MktSessions").ReturnsCollection<MktSession>();
function.Parameter<string>("Symbol");
function.Parameter<DateTimeOffset>("Begin");
function.Parameter<DateTimeOffset>("End");
and the controller has the following signature function:
[HttpGet]
[ODataRoute("MktSessions(Symbol={symbol},Begin={begin},End={end}")]
public IEnumerable<Reflexo.Api.MktSession> MktSessions (string symbol, DateTime begin, DateTime end) {
SOMETHING is wrong here. As long as the ODataRouter attbribute is presend, any call to $metadata resunlts in:
An error has occurred.
The object has not yet been initialized. Ensure that
HttpConfiguration.EnsureInitialized() is called in the application's
startup code after all other initialization code.
System.InvalidOperationException
at
System.Web.OData.Routing.Conventions.AttributeRoutingConvention.get_AttributeMappings()
at
System.Web.OData.Routing.Conventions.AttributeRoutingConvention.SelectController(ODataPath
odataPath, HttpRequestMessage request) at
System.Web.OData.Routing.ODataPathRouteConstraint.SelectControllerName(ODataPath
path, HttpRequestMessage request) at
System.Web.OData.Routing.ODataPathRouteConstraint.Match(HttpRequestMessage
request, IHttpRoute route, String parameterName, IDictionary`2 values,
HttpRouteDirection routeDirection) at
System.Web.Http.Routing.HttpRoute.ProcessConstraint(HttpRequestMessage
request, Object constraint, String parameterName,
HttpRouteValueDictionary values, HttpRouteDirection routeDirection) at
System.Web.Http.Routing.HttpRoute.ProcessConstraints(HttpRequestMessage
request, HttpRouteValueDictionary values, HttpRouteDirection
routeDirection) at
System.Web.Http.Routing.HttpRoute.GetRouteData(String virtualPathRoot,
HttpRequestMessage request) at
System.Web.Http.HttpRouteCollection.GetRouteData(HttpRequestMessage
request) at
System.Web.Http.Dispatcher.HttpRoutingDispatcher.SendAsync(HttpRequestMessage
request, CancellationToken cancellationToken) at
System.Net.Http.DelegatingHandler.SendAsync(HttpRequestMessage
request, CancellationToken cancellationToken) at
System.Web.Http.HttpServer.d__0.MoveNext()
which is as useless as it gets as an error message. I never have a chance to see original exception and this one jsut tells me the config is not there - which has no direct resemblence of the original error.
If I remove the ODataRoute attribute it works - but I can obviously not call the function.
Is there any way I am overlooking to actually get a meaningfull error message from this? Obviously the ODataRoute is somewhere in error (anyone knows where?) and a sensible "Parameter name blablbla does not match" text somewhere would be really helpfull.
There are two issues with the code your paste,
1. In the ODataRoute, you miss ")" after "{end}"
2. You should have define MktSessions as entity set, so your unbound function route can not be same as an entity set, you can change it to something else like RetrieveMktSessions as EntitySet query is enable by get method in controller but not a unbound function.
Let us know if you have any more issues.
Answering myself.
Now, on the side question there is a missing ")" at the end of the template.
More important, though.
When I add a call to HttpConfiguration.EnsureInitialized() to the end of the Owin configuration then I get the exception thrown there. This exception - while not having an inner exception - contains a meaningfull message that is lost in the web page output. This allows much better debugging.
Related
Does ASP.NET Web API 2 (not core) contain also something like [FromForm] attribute for binding the action? Or can I add the dll from ASP.NET Core to a normal ASP.NET Web API 2? {
"Message": "The request entity's media type 'multipart/form-data' is not supported for this resource.",
"ExceptionMessage": "No MediaTypeFormatter is available to read an object of type 'FooBindModel' from content with media type
'multipart/form-data'.",
"ExceptionType": "System.Net.Http.UnsupportedMediaTypeException",
"StackTrace": " at System.Net.Http.HttpContentExtensions.ReadAsAsync[T](HttpContent
content, Type type, IEnumerable1 formatters, IFormatterLogger
formatterLogger, CancellationToken cancellationToken)\r\n at
System.Web.Http.ModelBinding.FormatterParameterBinding.ReadContentAsync(HttpRequestMessage
request, Type type, IEnumerable1 formatters, IFormatterLogger
formatterLogger, CancellationToken cancellationToken)" }
No, only FromUri and FromBody is available for ASP.NET Web API 2. You can look at the official documentation.
Also, why would you try to implement FromForm to ASP.NET Web API 2. You can simply configure your form action as GET and POST and pass the data to Web Api. In my opinion, it would be over engineering.
You have to use FromUri for your model and also use HttpContext.Current.Request.Files for getting files from the header
this is your model that set in the Params section
this is your file that set in the Body section ==> from-data
and this is final result
I don't think you actually understand these attributes or what they do. [FromBody] means that you are providing a serialization (JSON, XML, etc.) or basically any other content that doesn't have an application/x-www-form-urlencoded mime type (image or other file type). If you're sending a normal form post (application/x-www-form-urlencoded request body) to a param marked as [FromBody], of course it won't bind, because you're explicitly telling it that it should be expecting something other than what you're sending.
Something like [FromForm] is actually pretty useless anyways, as the default is to expect application/x-www-form-urlencoded, which is all this attribute would do. Long and short, just don't decorate your action param with anything.
I have an OData controller with standard verbs for CRUD. Everything is working fine. Now I need to add a custom action to perform file upload. I try to add a method to my existing controller like this:
[HttpPost]
[Route("UploadFile")]
public async Task<HttpResponseMessage> UploadFile()
{
//handle uploaded content logic here...
}
But when I try to invoke it by doing a POST:
http://localhost/UploadFile
I get this error:
System.InvalidOperationException: No non-OData HTTP route registered.
What should I do for this custom action to allow file upload?
You need to declare the Action as part of the EdmModel, in the following example I am assuming that your Entity Type is Attachment, and your controller class name is AttachmentsController. By convention, your EntitySet name must then be Attachments
var attachments = builder.EntitySet<Attachment>("Attachments");
attachments.Action(nameof(AttachmentsController.UploadFile))
.Returns<System.Net.Http.HttpResponseMessage>();
The important part of the above statement is the return type, if you do not declare the return type correctly in your EdmModel then you will find your endpoints returning 406 errors - Unacceptable, even though your method executes correctly, which is really confusing the first time you run into it. This is because OData will still try to parse your response to match the Accept header from the request before completing the response.
Try to use 'nameof' when mapping functions and actions instead of 'magic strings' or constants so that the compiler can pickup basic issues like wrongly defined route.
With this approach you do not need the Route attribute on the method header and the action will be included in the metadata document and therefore swagger outputs.
We've got a set of MVC apis already in place, but I've been asked to put up OData versions. I'm trying to co-locate both sets in the same application to share things like custom role providers. I'm new to OData and so far MVC lessons don't seem to translate directly.
With MVC, we took the generic initialization and managed all the routes with RouteAttribute and I tried to go the same style using ODataRouteAttribute, but all my odata paths come up 404 and the $metadata query returns a 500 saying the configuration hasn't been initialized.
Here's the app start configuration chunk (ODataApiConfig added after the MVC one):
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
ODataApiConfig.Configure(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
GlobalConfiguration.Configuration.EnsureInitialized();
And here's what's in ODataApiConfig:
// OData v7 replacement for configuration.EnableCaseInsensitive(true);
configuration.MapODataServiceRoute("odata", "odata",
builder =>
builder.AddService<IEnumerable<IODataRoutingConvention>>(ServiceLifetime.Singleton, sp => ODataRoutingConventions.CreateDefaultWithAttributeRouting("odata", configuration))
.AddService<ODataUriResolver>(ServiceLifetime.Singleton, sp => new CaseInsensitiveResolver())
);
ODataModelBuilder modeler = new ODataConventionModelBuilder();
modeler.EntitySet<Task>("Tasks");
configuration.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "odata",
model: modeler.GetEdmModel()
);
configuration.EnsureInitialized();
And here's an example of an api I'm trying to hook up (to get a list of tasks assigned to an employee):
[HttpGet]
[EnableQuery]
[ODataRoute("v0.1/employee/{userGuid}/tasks")]
[ODataAuthorize(Roles = "Assignee")]
public IHttpActionResult GetMyTasks([FromODataUri]string userGuid)
{
...
}
So I'm puzzled why
a) A call to ~/odata/$metadata throws a "The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code." I tried calling EnsureInitialized() at the end of both the OData/MVC initialization and inside the OData initialize routine and it still throws the same error.
b) My ~/odata/employees/{userGuid}/tasks path comes up 404.
Any pointers will be appreciated.
Thanks
EDIT: It appears that the ~/odata/$metadata exception is related to the use of [ODataRoute] attributes. That seems to be enabled by default (and causing the exception); when I tried explicitly creating a routing convention object and DI-ing it, the $metadata exception went away. Didn't produce much useful, but the exception went away.
The stack trace on the exception is below; I tried breaking on any InvalidOperationExceptions thrown but did not get a break in the debugger
{"Message":"An error has occurred.","ExceptionMessage":"The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code.","ExceptionType":"System.InvalidOperationException","StackTrace":" at System.Web.OData.Routing.Conventions.AttributeRoutingConvention.get_AttributeMappings()
at System.Web.OData.Routing.Conventions.AttributeRoutingConvention.SelectController(ODataPath odataPath, HttpRequestMessage request)
at System.Web.OData.Routing.ODataPathRouteConstraint.SelectControllerName(ODataPath path, HttpRequestMessage request)
at System.Web.OData.Routing.ODataPathRouteConstraint.Match(HttpRequestMessage request, IHttpRoute route, String parameterName, IDictionary`2 values, HttpRouteDirection routeDirection)
at System.Web.Http.Routing.HttpRoute.ProcessConstraint(HttpRequestMessage request, Object constraint, String parameterName, HttpRouteValueDictionary values, HttpRouteDirection routeDirection)
at System.Web.Http.Routing.HttpRoute.ProcessConstraints(HttpRequestMessage request, HttpRouteValueDictionary values, HttpRouteDirection routeDirection)\r\n at System.Web.Http.Routing.HttpRoute.GetRouteData(String virtualPathRoot, HttpRequestMessage request)
at System.Web.Http.WebHost.Routing.HttpWebRoute.GetRouteData(HttpContextBase httpContext)"}
EDIT: At least I've got an answer on the $metadata call.
My initialization sequence had been informed by this article : https://blogs.msdn.microsoft.com/davidhardin/2014/12/17/web-api-odata-v4-lessons-learned/
which said that you had to initialize the Web Api calls first. I found the opposite. The above exception came as a result of initializing Web Api first. When I flipped them, the $metadata call started returning my Task entity set.
Unfortunately, my [ODataRoute] declarations in my TasksController still aren't getting routed.
Okay, the issue seems to be that some of the advice in here is out-dated:
https://blogs.msdn.microsoft.com/davidhardin/2014/12/17/web-api-odata-v4-lessons-learned/
Specifically, the $metadata exception turned out to be caused by setting up OData after Web API (per the above article). You have to initialize OData before Web Api.
Second, the point about controllers finding differentiation with the route prefix is wrong. I did segregate my web api controllers and odata controllers in different directories and I did use RoutePrefix/ODataRoutePrefix to put them on different paths.
But after getting the source code for AttributeRoutingConvention from github and using that for debugging, I found the following:
If you have 2 controllers with the same class name (e.g. both are called TaskController), even though they have different prefix paths and live in different directories, they collide. Both get dropped silently from the processing, so they are not found when a request comes in. Hence the 404.
So regardless of the prefixes and route attributes, the classes need to be named differently to be included.
I've installed Visual Studio 2013 and when I run my app I get the error below.
I've got no idea as to where I'm to initialized this object.
What to do?
Server Error in '/' Application.
The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code.
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[InvalidOperationException: The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code.]
System.Web.Http.Routing.RouteCollectionRoute.get_SubRoutes() +101
System.Web.Http.Routing.RouteCollectionRoute.GetRouteData(String virtualPathRoot, HttpRequestMessage request) +63
System.Web.Http.WebHost.Routing.HttpWebRoute.GetRouteData(HttpContextBase httpContext) +107
System.Web.Routing.RouteCollection.GetRouteData(HttpContextBase httpContext) +233
System.Web.Routing.UrlRoutingModule.PostResolveRequestCache(HttpContextBase context) +60
System.Web.Routing.UrlRoutingModule.OnApplicationPostResolveRequestCache(Object sender, EventArgs e) +82
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +136
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +69
Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.18408
This is for AlumCloud
If you do it at the end of Application_Start it will be too late, as WebApiConfig.Register has been called.
The best way to resolve this is to use new initialization method by replacing in Global.asax :
WebApiConfig.Register(GlobalConfiguration.Configuration);
by
GlobalConfiguration.Configure(WebApiConfig.Register);
See #gentiane's answer below for the correct way to handle this now.
At the end of the Application_Start method in Global.Asax.cs try adding:-
GlobalConfiguration.Configuration.EnsureInitialized();
I actually got this error when I was using Attribute Routing within my WebApi.
I had
[Route("webapi/siteTypes/{siteTypeId"]
instead of
[Route("webapi/siteTypes/{siteTypeId}"]
for my route and got this error. I had simply missed out the closing curly bracket. Once I added it back in, this error didn't occur again.
This is old, but is the first result on google when searching for this error. After quite a bit of digging I was able to figure out what was going on.
tldr:
All GlobalConfiguration.Configure does is invoke your action and call EnsureInitialized(). config.MapAttributeRoutes() must be called before EnsureInitialized() since EnsureInitialized only runs once.
Meaning: if you're coming from an existing Mvc project, all you have to do is:
Add
GlobalConfiguration.Configuration.EnsureInitialized();
to the bottom of your Application_Start method.
OR
Move your entire configuration into a single call to GlobalConfiguration.Configure:
GlobalConfiguration.Configure(config =>
{
WebApiConfig.Register(config);
config.MapAttributeRoutes();
...
});
Digging Deeper
HttpConfiguration.Configuration has an "Initializer" property defined like this:
public Action<HttpConfiguration> Initializer;
HttpConfiguration.EnsureInitialized() runs this action and sets _initialized to true
public void EnsureInitialized()
{
if (_initialized)
{
return;
}
_initialized = true;
Initializer(this);
}
HttpConfiguration.MapAttributeRoutes calls internal method AttributeRoutingMapper.MapAttributeRoutes which sets HttpConfiguration.Initializer
public static void MapAttributeRoutes(...)
{
RouteCollectionRoute aggregateRoute = new RouteCollectionRoute();
configuration.Routes.Add(AttributeRouteName, aggregateRoute);
...
Action<HttpConfiguration> previousInitializer = configuration.Initializer;
configuration.Initializer = config =>
{
previousInitializer(config);
...
};
}
GlobalConfiguration.Configure runs EnsureInitialized immediately after invoking your action:
public static void Configure(Action<HttpConfiguration> configurationCallback)
{
if (configurationCallback == null)
{
throw new ArgumentNullException("configurationCallback");
}
configurationCallback.Invoke(Configuration);
Configuration.EnsureInitialized();
}
Don't forget, if you run in to a wall, the source for asp.net is available at http://aspnetwebstack.codeplex.com/SourceControl/latest
I've had a related issue. Sometimes calling GlobalConfiguration.Configure multiple times triggers this error. As a workaround, I've put all configuration initialization logic in one place.
IF THIS ERROR SEEMS TO HAVE COME "OUT OF NOWHERE", i.e. your app was working perfectly fine for a while, ask yourself: Did I add an action to a controller or change any routes prior to seeing this error?
If the answer is yes (and it probably is), you likely made a mistake in the process. Incorrect formatting, copy/pasting an action and forgetting to make sure the endpoint names are unique, etc. will all end you up here. The suggestion that this error makes on how to resolve it can send you barking up the wrong tree.
For me, the problem was that I was trying to use named parameters for query string fields in my routes:
[Route("my-route?field={field}")]
public void MyRoute([FromUri] string field)
{
}
Query string fields are automatically mapped to parameters and aren't actually part of the route definition. This works:
[Route("my-route")]
public void MyRoute([FromUri] string field)
{
}
Although the above answer works if incase that is not set, In my case this stuff was set already. What was different was that, for one of the APIs I had written, I had prefixed the route with a / . Example
[Route("/api/abc/{client}")]
.Changing this to
[Route("api/abc/{client}")]
fixed it for me
Call
GlobalConfiguration.Configuration.MapHttpAttributeRoutes();
before
GlobalConfiguration.Configure(c => ...);
completes its execution.
I got this error when the version of Newtonsoft.Json was different in my main project compared to the helper project
One typically gets this exception when route templates in "Attribute Routing" are not proper.
For example, i got this when i wrote the following code:
[Route("{dirName:string}/contents")] //incorrect
public HttpResponseMessage GetDirContents(string dirName) { ... }
In route constraints syntax {parameter:constraint}, constraint by default is of type string. No need to mention it explicitly.
[Route("{dirName}/contents")] //correct
public HttpResponseMessage GetDirContents(string dirName) { ... }
I began getting this error one day. After I'd altered our app to call EnsureInitialized() I was able to see the root cause.
I had a custom attribute, a filter, on an action. That attribute class had had a breaking change made in the NuGet package in which it lives.
Even though I'd updated the the code and it all compiled, the local IIS worker was loading an old DLL and not finding a class member during initialization, reading attributes on actions etc.
For some reason (possibly due to order/when our logging is initialized), this error was not discoverable, possibly leaving the WebAPI in a strange state, until I'd added EnsureInitialized() which caught the exception and surfaced it.
Performing a proper bin and obj clean via a handy script resolved it.
In my case I created the webservice in project A and started it from Project B and I got exactly this error. The problem was that some .dll-files which are required by A where missing in the build-output-folder of B. Ensure that these .dll-files are available fixed it.
In my case , i used an Entity as parameter of my action that its 'Schema' is missing.
Wrong attribute:
[Table("Table name", Schema = "")]
Correct :
[Table("Table name", Schema = "schema name")]
In my case I fixed replacing:
<Route("{id:string}")>
with
<Route("{id}")>
Strange that I was sure that the original code was working before suddenly stopping, go figure....
I experienced this similar error.
<Error>
<Message>An error has occurred.</Message>
<ExceptionMessage>The object has not yet been initialized. Ensure that HttpConfiguration.EnsureInitialized() is called in the application's startup code after all other initialization code.</ExceptionMessage>
<ExceptionType>System.InvalidOperationException</ExceptionType>
<StackTrace> at System.Web.Http.Routing.RouteCollectionRoute.get_SubRoutes() at System.Web.Http.Routing.RouteCollectionRoute.GetRouteData(String virtualPathRoot, HttpRequestMessage request) at System.Web.Http.WebHost.Routing.HttpWebRoute.GetRouteData(HttpContextBase httpContext)</StackTrace>
</Error>
After fiddling with it for a while, I realized two problems: directory name collides with routing prefix and routing prefix must not end with '/'. When starting up the service for debugging I got this browser error after adding the SampleController.cs.
The folder structure
/api/Controllers/SampleController.cs
/api/Model/...
SampleController.cs
[RoutePrefix("api/sub/{parameterId}/more/")]
public class SampleController : ApiController
{
[HttpGet]
[Route("")]
public IHttpActionResult Get(int parameterId)
{
return Ok("Knock Knock...");
}
}
Changing the routing prefix to not end in '/' and not colliding with the directory name solved the problem.
I have an asp.net web api project. In my controllers I have set up the
ExceptionFilterAttribute
To catch any errors at a global level. There are two get requests being fired off from the controller method. They are failing and so I am seeing the exception being raised in the exception filter. However the exception is not showing me details of the failed request. Is it possible to get them? For example 4 GET requests might have been invoked and one of them is failing and the exception is being thrown. But all im seeing is a message saying...
The remote name could not be resolved: 'xx.xx.com'
But I need more details, like the query string etc...
The response object on the web exception is null too :-(
Within the OnException method of your ExceptionFilterAttribute you have a parameter argument of type HttpActionExecutedContext. Within the instance of this class you can access the Request and Response properties to get all the information you need, either of the request or the response. Within the ActionContext property you can even get all routing, controller and action informations.
var requestHttpMethod = actionExecutedContext.Request.Method;
var requestUri = actionExecutedContext.Request.RequestUri;
var controllerDescriptor = actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor;
var actionDescriptor = actionExecutedContext.ActionContext.ActionDescriptor;
You can also modify the response object to return a more appropriated error message. Just look around a bit what information these classes can provide you.