This is especially impactful for OData where we've had to implement content negotiators and value binders to get this functionality. And GetPerRequestFormatterInstance is affecting performance on both the read size and the write side.
↧
Edited Feature: Reconsider how to pass the request/response to media type formatters [779]
↧
Edited Issue: DependencyScope is disposed before the HttpControllerTracer [768]
### Facts
- I am using AutoFac as IoC container
- I enabled tracing with my custom tracer.
- During the trace operation, I try to get the `DependencyScope` through request.Properties collection.
### Result
At the end of the request (when the response is ready to go out the door), `System.Net.Http.HttpRequestMessageExtensions.DisposeRequestResources` is called to dispose the list of disposables registered inside the `request.Properties["MS_DisposableRequestResources"]`. When tracing is enabled and I am using AutoFac, there are two registered disposables (in the same order as below):
- `Autofac.Integration.WebApi.AutofacWebApiDependencyScope`
- `System.Web.Http.Tracing.Tracers.HttpControllerTracer`
As `DisposeRequestResources` method goes through the list in order, `AutofacWebApiDependencyScope` is disposed before the `HttpControllerTracer`. The funny thing is that `HttpControllerTracer.Dispose` calls the registered tracer to write the End trace and as my trace needs the `DependencyScope`, it blows with the below error since the `DependencyScope` is now disposed:
> {"Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed."}
>
> at Autofac.Core.Lifetime.LifetimeScope.CheckNotDisposed()
> at Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters)
> at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance)
> at Autofac.ResolutionExtensions.ResolveOptionalService(IComponentContext context, Service service, IEnumerable`1 parameters)
> at Autofac.ResolutionExtensions.ResolveOptional(IComponentContext context, Type serviceType, IEnumerable`1 parameters)
> at Autofac.ResolutionExtensions.ResolveOptional(IComponentContext context, Type serviceType)
> at Autofac.Integration.WebApi.AutofacWebApiDependencyScope.GetService(Type serviceType)
> at MyProject.HttpRequestMessageExtensions.GetService[TService](HttpRequestMessage request) in e:\Dropbox\Apps\Clients\Serdar\MyProject\src\MyProject.API\Http\HttpRequestMessageExtensions.cs:line 64
> at MyProject.Http.HttpRequestMessageExtensions.GetLoggingService(HttpRequestMessage request) in e:\Dropbox\Apps\Clients\Serdar\MyProject\src\MyProject.API\Http\HttpRequestMessageExtensions.cs:line 33
> at MyProject.API.Tracing.MyProjectTracer.Log(TraceRecord traceRecord) in e:\Dropbox\Apps\Clients\Serdar\MyProject\src\MyProject.API\Tracing\MyProjectTracer.cs:line 23
> at MyProject.API.Tracing.MyProjectTracer.Trace(HttpRequestMessage request, String category, TraceLevel level, Action`1 traceAction) in e:\Dropbox\Apps\Clients\Serdar\MyProject\src\MyProject.API\Tracing\MyProjectTracer.cs:line 17
> at System.Web.Http.Tracing.ITraceWriterExtensions.TraceBeginEnd(ITraceWriter traceWriter, HttpRequestMessage request, String category, TraceLevel level, String operatorName, String operationName, Action`1 beginTrace, Action execute, Action`1 endTrace, Action`1 errorTrace)
> at System.Web.Http.Tracing.Tracers.HttpControllerTracer.System.IDisposable.Dispose()
> at System.Net.Http.HttpRequestMessageExtensions.DisposeRequestResources(HttpRequestMessage request)
## Suggestion
`System.Net.Http.HttpRequestMessageExtensions.DisposeRequestResources` **should** have a deep knowledge about the `DependencyScope` and **should** dispose it lastly.
### Workaround
There are several workarounds for that (replacing the HttpControllerTracer, etc.) but the one I applied is very dirty. Adding a message handler as the first message handler (so that it runs last (just in case)) and reordering the disposables on the way out:
public class DisposableRequestResourcesReorderHandler : DelegatingHandler {
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
return base.SendAsync(request, cancellationToken).Finally(() => {
List<IDisposable> disposableResources = request.Properties[HttpPropertyKeys.DisposableRequestResourcesKey] as List<IDisposable>;
if (disposableResources != null && disposableResources.Count > 1) {
// 1-) Get the first one (which I know is AutofacWebApiDependencyScope).
// 2-) Remove it from the list.
// 3-) Push it at the end of the list.
IDisposable dependencyScope = disposableResources[0];
disposableResources.RemoveAt(0);
disposableResources.Add(dependencyScope);
}
}, runSynchronously: true);
}
}
- I am using AutoFac as IoC container
- I enabled tracing with my custom tracer.
- During the trace operation, I try to get the `DependencyScope` through request.Properties collection.
### Result
At the end of the request (when the response is ready to go out the door), `System.Net.Http.HttpRequestMessageExtensions.DisposeRequestResources` is called to dispose the list of disposables registered inside the `request.Properties["MS_DisposableRequestResources"]`. When tracing is enabled and I am using AutoFac, there are two registered disposables (in the same order as below):
- `Autofac.Integration.WebApi.AutofacWebApiDependencyScope`
- `System.Web.Http.Tracing.Tracers.HttpControllerTracer`
As `DisposeRequestResources` method goes through the list in order, `AutofacWebApiDependencyScope` is disposed before the `HttpControllerTracer`. The funny thing is that `HttpControllerTracer.Dispose` calls the registered tracer to write the End trace and as my trace needs the `DependencyScope`, it blows with the below error since the `DependencyScope` is now disposed:
> {"Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed."}
>
> at Autofac.Core.Lifetime.LifetimeScope.CheckNotDisposed()
> at Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters)
> at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance)
> at Autofac.ResolutionExtensions.ResolveOptionalService(IComponentContext context, Service service, IEnumerable`1 parameters)
> at Autofac.ResolutionExtensions.ResolveOptional(IComponentContext context, Type serviceType, IEnumerable`1 parameters)
> at Autofac.ResolutionExtensions.ResolveOptional(IComponentContext context, Type serviceType)
> at Autofac.Integration.WebApi.AutofacWebApiDependencyScope.GetService(Type serviceType)
> at MyProject.HttpRequestMessageExtensions.GetService[TService](HttpRequestMessage request) in e:\Dropbox\Apps\Clients\Serdar\MyProject\src\MyProject.API\Http\HttpRequestMessageExtensions.cs:line 64
> at MyProject.Http.HttpRequestMessageExtensions.GetLoggingService(HttpRequestMessage request) in e:\Dropbox\Apps\Clients\Serdar\MyProject\src\MyProject.API\Http\HttpRequestMessageExtensions.cs:line 33
> at MyProject.API.Tracing.MyProjectTracer.Log(TraceRecord traceRecord) in e:\Dropbox\Apps\Clients\Serdar\MyProject\src\MyProject.API\Tracing\MyProjectTracer.cs:line 23
> at MyProject.API.Tracing.MyProjectTracer.Trace(HttpRequestMessage request, String category, TraceLevel level, Action`1 traceAction) in e:\Dropbox\Apps\Clients\Serdar\MyProject\src\MyProject.API\Tracing\MyProjectTracer.cs:line 17
> at System.Web.Http.Tracing.ITraceWriterExtensions.TraceBeginEnd(ITraceWriter traceWriter, HttpRequestMessage request, String category, TraceLevel level, String operatorName, String operationName, Action`1 beginTrace, Action execute, Action`1 endTrace, Action`1 errorTrace)
> at System.Web.Http.Tracing.Tracers.HttpControllerTracer.System.IDisposable.Dispose()
> at System.Net.Http.HttpRequestMessageExtensions.DisposeRequestResources(HttpRequestMessage request)
## Suggestion
`System.Net.Http.HttpRequestMessageExtensions.DisposeRequestResources` **should** have a deep knowledge about the `DependencyScope` and **should** dispose it lastly.
### Workaround
There are several workarounds for that (replacing the HttpControllerTracer, etc.) but the one I applied is very dirty. Adding a message handler as the first message handler (so that it runs last (just in case)) and reordering the disposables on the way out:
public class DisposableRequestResourcesReorderHandler : DelegatingHandler {
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
return base.SendAsync(request, cancellationToken).Finally(() => {
List<IDisposable> disposableResources = request.Properties[HttpPropertyKeys.DisposableRequestResourcesKey] as List<IDisposable>;
if (disposableResources != null && disposableResources.Count > 1) {
// 1-) Get the first one (which I know is AutofacWebApiDependencyScope).
// 2-) Remove it from the list.
// 3-) Push it at the end of the list.
IDisposable dependencyScope = disposableResources[0];
disposableResources.RemoveAt(0);
disposableResources.Add(dependencyScope);
}
}, runSynchronously: true);
}
}
↧
↧
Created Unassigned: Provide a default implementation for changeset unit of work semantics in OData $batch [1096]
The current implementation of DefaultODataBatchHandler doesn't provide any kind of Unit Of Work semantics as defined for changesets in the OData V3 specification. The current implementation processes the changeset but doesn't perform any kind of rollback operation in case of failure, which is the wrong default behavior.
Provide an abstract method that the user needs to implement in order to provide rollback semantics to be able to use an ODataBatchHandler and provide a default implementation in the DefaultODataBatchHandler that handles the majority of the use cases.
The excerpt from the ODataspec:
> "All operations in a ChangeSet represent a single change unit so a service MUST successfully process and apply all the requests in the ChangeSet or else apply none of them. It is up to the service implementation to define rollback semantics to undo any requests within a ChangeSet that may have been applied before another request in that same ChangeSet failed and thereby honor this all-or-nothing requirement"
Provide an abstract method that the user needs to implement in order to provide rollback semantics to be able to use an ODataBatchHandler and provide a default implementation in the DefaultODataBatchHandler that handles the majority of the use cases.
The excerpt from the ODataspec:
> "All operations in a ChangeSet represent a single change unit so a service MUST successfully process and apply all the requests in the ChangeSet or else apply none of them. It is up to the service implementation to define rollback semantics to undo any requests within a ChangeSet that may have been applied before another request in that same ChangeSet failed and thereby honor this all-or-nothing requirement"
↧
Edited Issue: $expand fails when the navigation property being expanded is null. [1043]
**Url**
`/api/passos?$expand=ProximoPasso&$select=Nome,ProximoPasso/Nome`
**Code**
```
public abstract class EntityNome : IEntity
{
public int Id { get; set; }
public string Nome { get; set; }
}
```
```
public class Passo : EntityNome, IAuditavel
{
public virtual Passo ProximoPasso { get; set; }
public virtual ICollection<Usuario> Responsaveis { get; set; }
public virtual ICollection<CheckListItemTemplate> CheckListItens { get; set; }
...
```
**Error**
```
{
"odata.error": {
"code": "",
"message": {
"lang": "en-US",
"value": "An error has occurred."
},
"innererror": {
"message": "Exception has been thrown by the target of an invocation.",
"type": "System.Reflection.TargetInvocationException",
"stacktrace": " at System.Web.Http.ApiController.<InvokeActionWithExceptionFilters>d__a.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__0.MoveNext()",
"internalexception": {
"message": "The cast to value type 'Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.",
"type": "System.InvalidOperationException",
"stacktrace": " at System.Data.Entity.Core.Common.Internal.Materialization.Shaper.ErrorHandlingValueReader`1.GetValue(DbDataReader reader, Int32 ordinal)\r\n at lambda_method(Closure , Shaper )\r\n at System.Data.Entity.Core.Common.Internal.Materialization.Coordinator`1.ReadNextElement(Shaper shaper)\r\n at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.SimpleEnumerator.MoveNext()\r\n at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()\r\n at System.Web.Http.OData.Query.ODataQueryOptions.LimitResults[T](IQueryable`1 queryable, Int32 limit, Boolean& resultsLimited)"
}
}
}
}
```
`/api/passos?$expand=ProximoPasso&$select=Nome,ProximoPasso/Nome`
**Code**
```
public abstract class EntityNome : IEntity
{
public int Id { get; set; }
public string Nome { get; set; }
}
```
```
public class Passo : EntityNome, IAuditavel
{
public virtual Passo ProximoPasso { get; set; }
public virtual ICollection<Usuario> Responsaveis { get; set; }
public virtual ICollection<CheckListItemTemplate> CheckListItens { get; set; }
...
```
**Error**
```
{
"odata.error": {
"code": "",
"message": {
"lang": "en-US",
"value": "An error has occurred."
},
"innererror": {
"message": "Exception has been thrown by the target of an invocation.",
"type": "System.Reflection.TargetInvocationException",
"stacktrace": " at System.Web.Http.ApiController.<InvokeActionWithExceptionFilters>d__a.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__0.MoveNext()",
"internalexception": {
"message": "The cast to value type 'Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.",
"type": "System.InvalidOperationException",
"stacktrace": " at System.Data.Entity.Core.Common.Internal.Materialization.Shaper.ErrorHandlingValueReader`1.GetValue(DbDataReader reader, Int32 ordinal)\r\n at lambda_method(Closure , Shaper )\r\n at System.Data.Entity.Core.Common.Internal.Materialization.Coordinator`1.ReadNextElement(Shaper shaper)\r\n at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.SimpleEnumerator.MoveNext()\r\n at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()\r\n at System.Web.Http.OData.Query.ODataQueryOptions.LimitResults[T](IQueryable`1 queryable, Int32 limit, Boolean& resultsLimited)"
}
}
}
}
```
↧
Edited Issue: Can't unit test code that generates OData links [769]
There's no unit that encapsulates the logic around generating a full OData link, so tests end up integration style. Having an odata link factory abstraction allows unit testing such code.
We are putting two related properties together on the request: path handler and route name. These are a pair, which is a code smell that there's one abstraction here that should be used instead. Having an odata link factor also solves this problem.
We are putting two related properties together on the request: path handler and route name. These are a pair, which is a code smell that there's one abstraction here that should be used instead. Having an odata link factor also solves this problem.
↧
↧
Edited Issue: Emit navigation properties with source multiplicity '*' if the target multiplicity is one. [792]
we emit properties with source multiplicity '0..1' by default if the target multiplicity is 1. users cannot configure the source end of the multiplicity. So, we should emit a better default which in this case is '*'.
↧
Commented Issue: Emit navigation properties with source multiplicity '*' if the target multiplicity is one. [792]
we emit properties with source multiplicity '0..1' by default if the target multiplicity is 1. users cannot configure the source end of the multiplicity. So, we should emit a better default which in this case is '*'.
Comments: Please take into consideration any aspects of this that could be a breaking change. We'll want to consider a switch of some sort to get the old behavior.
Comments: Please take into consideration any aspects of this that could be a breaking change. We'll want to consider a switch of some sort to get the old behavior.
↧
Edited Issue: Direct link generation should be consistent with Url.Link when the OData path is empty and there is a route prefix [793]
The direct link generation generates "http://localhost/prefix/", while Url.Link generates "http://localhost/prefix" when the OData path is empty. There's a slight inconsistency here that we should fix.
↧
Edited Issue: Expose queryable mode option in ODataConvetionModelBuilder [804]
This is useful for unit testing following actions:
IQueryable<string> Get(ODataQueryOptions<Todo> options)
Users have to build options by themselves and in that case, they need a queryable mode model. Otherwise, the model builder won't work for some cases like no-ID entities.
IQueryable<string> Get(ODataQueryOptions<Todo> options)
Users have to build options by themselves and in that case, they need a queryable mode model. Otherwise, the model builder won't work for some cases like no-ID entities.
↧
↧
Edited Issue: Document dependency between EntitySetController.CreateEntity and GetKey methods [842]
If you override CreateEntity and do not override GetKey, you get an obscure error when calling the service:
{
"odata.error":{
"code":"POST requests are not supported.","message":{
"lang":"en-US","value":"Extracting the key from entity instances is not supported for this entity set."
}
}
}
This is because CreateEntity calls GetKey under the covers. The XML Doc comments on CreateEntity should document the dependency and the fact that you have to override both.
{
"odata.error":{
"code":"POST requests are not supported.","message":{
"lang":"en-US","value":"Extracting the key from entity instances is not supported for this entity set."
}
}
}
This is because CreateEntity calls GetKey under the covers. The XML Doc comments on CreateEntity should document the dependency and the fact that you have to override both.
↧
Commented Issue: Document dependency between EntitySetController.CreateEntity and GetKey methods [842]
If you override CreateEntity and do not override GetKey, you get an obscure error when calling the service:
{
"odata.error":{
"code":"POST requests are not supported.","message":{
"lang":"en-US","value":"Extracting the key from entity instances is not supported for this entity set."
}
}
}
This is because CreateEntity calls GetKey under the covers. The XML Doc comments on CreateEntity should document the dependency and the fact that you have to override both.
Comments: Let's just update the XML documentation on this to make it clearer how to use it.
{
"odata.error":{
"code":"POST requests are not supported.","message":{
"lang":"en-US","value":"Extracting the key from entity instances is not supported for this entity set."
}
}
}
This is because CreateEntity calls GetKey under the covers. The XML Doc comments on CreateEntity should document the dependency and the fact that you have to override both.
Comments: Let's just update the XML documentation on this to make it clearer how to use it.
↧
Edited Issue: Add AllowedFilterProperties to the Queryable attribute [851]
Similar to AllowedOrderByProperties.
↧
Edited Issue: Overriden CreateLink method does not work [888]
I derive my controller class from EntitySetController<TEntity, TKey>
I have overriden the CreateLink method of base class:
```
public override void CreateLink(int key, string navigationProperty, Uri link)
{
}
```
The link parameter argument is always passed in as null, even though the body contains an Url:
```
POST http://localhost:34852/Channels(1)/$links/SIGNALS HTTP/1.1
Accept: application/json;odata=minimalmetadata
DataServiceVersion: 3.0,3.0;
MaxDataServiceVersion: 3.0
Content-Type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8
Host: localhost:34852
Content-Length: 53
Expect: 100-continue
{
"url":"http://localhost:34852/Signals(20000)"
}
```
Workaround:
It only works correctly by specifying again the [FromBody] attribute in the method signature of overriden method:
```
public override void CreateLink(int key, string navigationProperty, [FromBody] Uri link)
{
}
```
I believe this is a bug, since the mechanism works with the key parameter, where [FromODataUri] attribute is specified in the base class and I do not have to repeat it in overidden method.
I have overriden the CreateLink method of base class:
```
public override void CreateLink(int key, string navigationProperty, Uri link)
{
}
```
The link parameter argument is always passed in as null, even though the body contains an Url:
```
POST http://localhost:34852/Channels(1)/$links/SIGNALS HTTP/1.1
Accept: application/json;odata=minimalmetadata
DataServiceVersion: 3.0,3.0;
MaxDataServiceVersion: 3.0
Content-Type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8
Host: localhost:34852
Content-Length: 53
Expect: 100-continue
{
"url":"http://localhost:34852/Signals(20000)"
}
```
Workaround:
It only works correctly by specifying again the [FromBody] attribute in the method signature of overriden method:
```
public override void CreateLink(int key, string navigationProperty, [FromBody] Uri link)
{
}
```
I believe this is a bug, since the mechanism works with the key parameter, where [FromODataUri] attribute is specified in the base class and I do not have to repeat it in overidden method.
↧
↧
Edited Feature: [OData] Add support for nested paging. [1016]
Now that we have $expand support, we should add support for server-driven paging for expanded feeds.
↧
Created Issue: Add simple support for OData nested paging [1101]
Implement nested paging where every level is paged at the same size.
↧
Edited Issue: Make ODataQueryOptions behave more like a property [1061]
As of right now, every time the user calls EntitySetController.QueryOptions we create a new instance of ODataQueryOptions<T>.
This causes some problems when the user calls it several times and makes changes on one of the instances as the customer expects the same instance to be returned every time he calls EntitySetController.QueryOptions.
This causes some problems when the user calls it several times and makes changes on one of the instances as the customer expects the same instance to be returned every time he calls EntitySetController.QueryOptions.
↧
Edited Issue: Add a Request.CreateResponse overload that takes the content CLR type as parameter. [883]
Request.CreateResponse<T> doesn't use the runtime type of the object for content-negotiation. This would be an issue when the formatter doesn't responds to CanWriteType(typeof(object)) with false (example ODataMediaTypeFormatter). An example scenario can be found [here](https://gist.github.com/raghuramn/5084608).
Having a non-generic overload where we can specify T would be useful.
Having a non-generic overload where we can specify T would be useful.
↧
↧
Edited Issue: $expand breaks use of Get(ODataQueryOptions) [1073]
Bug, or "how do I do this", in Web API OData:
Using the nightly builds to test out $expand support - note that I really really want to switch to Web API OData over WCF Data Services, because the extension points are soo much better, but I can't justify doing so until I can provide equivalent functionality.
I want to inject `ODataValidationSettings` (so I can manage them centrally instead of in per-controller attributes). I can't do that using EntitySetController, so I had to create my own base class. Here's one implementation:
``` C#
public abstract class DbSetController<TEntity, TKey, TDbContext> : ODataController
where TEntity : class
where TDbContext : DbContext
{
private readonly ODataValidationSettings _queryValidationSettings;
private readonly TDbContext _dbContext;
protected DbSetController(ODataValidationSettings queryValidationSettings, TDbContext dbContext)
{
_queryValidationSettings = queryValidationSettings;
_dbContext = dbContext;
}
protected virtual ODataValidationSettings QueryValidationSettings
{
get { return _queryValidationSettings; }
}
public virtual IQueryable<TEntity> Get(ODataQueryOptions<TEntity> queryOptions)
{
queryOptions.Validate(QueryValidationSettings);
IQueryable queryApplied = queryOptions.ApplyTo(_dbContext.Set<TEntity>());
return queryApplied.Cast<TEntity>();
}
```
This works fine for simple queries, like http://localhost/odata/Status?$top=2 .
However, it breaks as soon as I add an $expand or $select clause - I get:
``` xml
<m:internalexception>
<m:message>Unable to cast the type 'System.Web.Http.OData.Query.Expressions.SelectExpandWrapper`1' to type 'Scrum.Model.WorkItem'. LINQ to Entities only supports casting EDM primitive or enumeration types.</m:message>
<m:type>System.NotSupportedException</m:type>
<m:stacktrace> at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.ValidateAndAdjustCastTypes(TypeUsage toType, TypeUsage fromType, Type toClrType, Type fromClrType)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.GetCastTargetType(TypeUsage fromType, Type toClrType, Type fromClrType, Boolean preserveCastForDateTime)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.CreateCastExpression(DbExpression source, Type toClrType, Type fromClrType)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.CastMethodTranslator.Translate(ExpressionConverter parent, MethodCallExpression call)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.SequenceMethodTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, SequenceMethod sequenceMethod)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.Convert()
at System.Data.Entity.Core.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable`1 forMergeOption)
at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClassb.<GetResults>b__a()
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, Boolean throwOnExistingTransaction, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClassb.<GetResults>b__9()
at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.ProtectedExecute[TResult](Func`1 func)
at System.Data.Entity.Infrastructure.ExecutionStrategy.Execute[TResult](Func`1 func)
at System.Data.Entity.Core.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
at System.Data.Entity.Core.Objects.ObjectQuery`1.<System.Collections.Generic.IEnumerable<T>.GetEnumerator>b__0()
at System.Lazy`1.CreateValue()
at System.Lazy`1.LazyInitValue()
at System.Lazy`1.get_Value()
at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteFeed(IEnumerable enumerable, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObjectInline(Object graph, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObject(Object graph, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)
at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__10.MoveNext()</m:stacktrace>
</m:internalexception>
```
Figuring that it may be an issue with entity framework not liking the other types in its expression tree, I tried this implementation:
``` C#
public virtual HttpResponseMessage Get(ODataQueryOptions<TEntity> queryOptions)
{
queryOptions.Validate(QueryValidationSettings);
IQueryable queryApplied = queryOptions.ApplyTo(_dbContext.Set<TEntity>());
var list = new List<object>(100);
foreach (var o in queryApplied)
{
list.Add(o);
}
return Request.CreateResponse(list.Cast<TEntity>());
}
```
This breaks as follows:
``` xml
<?xml version="1.0" encoding="utf-8"?>
<m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<m:code />
<m:message xml:lang="en-US">An error has occurred.</m:message>
<m:innererror>
<m:message>The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.</m:message>
<m:type>System.InvalidOperationException</m:type>
<m:stacktrace></m:stacktrace>
<m:internalexception>
<m:message>Unable to cast object of type 'System.Web.Http.OData.Query.Expressions.SelectExpandWrapper`1[Scrum.Model.WorkItem]' to type 'Scrum.Model.WorkItem'.</m:message>
<m:type>System.InvalidCastException</m:type>
<m:stacktrace> at System.Linq.Enumerable.<CastIterator>d__b1`1.MoveNext()
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteFeed(IEnumerable enumerable, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObjectInline(Object graph, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObject(Object graph, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)
at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__10.MoveNext()</m:stacktrace>
</m:internalexception>
</m:innererror>
</m:error>
```
It looks like I can extract the element values from `SelectExpandWrapper`, but it's not clear that's the right way to go - please point me at a better example if there's a better approach.
Using the nightly builds to test out $expand support - note that I really really want to switch to Web API OData over WCF Data Services, because the extension points are soo much better, but I can't justify doing so until I can provide equivalent functionality.
I want to inject `ODataValidationSettings` (so I can manage them centrally instead of in per-controller attributes). I can't do that using EntitySetController, so I had to create my own base class. Here's one implementation:
``` C#
public abstract class DbSetController<TEntity, TKey, TDbContext> : ODataController
where TEntity : class
where TDbContext : DbContext
{
private readonly ODataValidationSettings _queryValidationSettings;
private readonly TDbContext _dbContext;
protected DbSetController(ODataValidationSettings queryValidationSettings, TDbContext dbContext)
{
_queryValidationSettings = queryValidationSettings;
_dbContext = dbContext;
}
protected virtual ODataValidationSettings QueryValidationSettings
{
get { return _queryValidationSettings; }
}
public virtual IQueryable<TEntity> Get(ODataQueryOptions<TEntity> queryOptions)
{
queryOptions.Validate(QueryValidationSettings);
IQueryable queryApplied = queryOptions.ApplyTo(_dbContext.Set<TEntity>());
return queryApplied.Cast<TEntity>();
}
```
This works fine for simple queries, like http://localhost/odata/Status?$top=2 .
However, it breaks as soon as I add an $expand or $select clause - I get:
``` xml
<m:internalexception>
<m:message>Unable to cast the type 'System.Web.Http.OData.Query.Expressions.SelectExpandWrapper`1' to type 'Scrum.Model.WorkItem'. LINQ to Entities only supports casting EDM primitive or enumeration types.</m:message>
<m:type>System.NotSupportedException</m:type>
<m:stacktrace> at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.ValidateAndAdjustCastTypes(TypeUsage toType, TypeUsage fromType, Type toClrType, Type fromClrType)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.GetCastTargetType(TypeUsage fromType, Type toClrType, Type fromClrType, Boolean preserveCastForDateTime)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.CreateCastExpression(DbExpression source, Type toClrType, Type fromClrType)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.CastMethodTranslator.Translate(ExpressionConverter parent, MethodCallExpression call)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.SequenceMethodTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, SequenceMethod sequenceMethod)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.Convert()
at System.Data.Entity.Core.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable`1 forMergeOption)
at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClassb.<GetResults>b__a()
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, Boolean throwOnExistingTransaction, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClassb.<GetResults>b__9()
at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.ProtectedExecute[TResult](Func`1 func)
at System.Data.Entity.Infrastructure.ExecutionStrategy.Execute[TResult](Func`1 func)
at System.Data.Entity.Core.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
at System.Data.Entity.Core.Objects.ObjectQuery`1.<System.Collections.Generic.IEnumerable<T>.GetEnumerator>b__0()
at System.Lazy`1.CreateValue()
at System.Lazy`1.LazyInitValue()
at System.Lazy`1.get_Value()
at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteFeed(IEnumerable enumerable, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObjectInline(Object graph, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObject(Object graph, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)
at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__10.MoveNext()</m:stacktrace>
</m:internalexception>
```
Figuring that it may be an issue with entity framework not liking the other types in its expression tree, I tried this implementation:
``` C#
public virtual HttpResponseMessage Get(ODataQueryOptions<TEntity> queryOptions)
{
queryOptions.Validate(QueryValidationSettings);
IQueryable queryApplied = queryOptions.ApplyTo(_dbContext.Set<TEntity>());
var list = new List<object>(100);
foreach (var o in queryApplied)
{
list.Add(o);
}
return Request.CreateResponse(list.Cast<TEntity>());
}
```
This breaks as follows:
``` xml
<?xml version="1.0" encoding="utf-8"?>
<m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<m:code />
<m:message xml:lang="en-US">An error has occurred.</m:message>
<m:innererror>
<m:message>The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.</m:message>
<m:type>System.InvalidOperationException</m:type>
<m:stacktrace></m:stacktrace>
<m:internalexception>
<m:message>Unable to cast object of type 'System.Web.Http.OData.Query.Expressions.SelectExpandWrapper`1[Scrum.Model.WorkItem]' to type 'Scrum.Model.WorkItem'.</m:message>
<m:type>System.InvalidCastException</m:type>
<m:stacktrace> at System.Linq.Enumerable.<CastIterator>d__b1`1.MoveNext()
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteFeed(IEnumerable enumerable, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObjectInline(Object graph, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObject(Object graph, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)
at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__10.MoveNext()</m:stacktrace>
</m:internalexception>
</m:innererror>
</m:error>
```
It looks like I can extract the element values from `SelectExpandWrapper`, but it's not clear that's the right way to go - please point me at a better example if there's a better approach.
↧
Edited Issue: EntitySetController.Get(TKey) breaks when $expand query options are added [1074]
This is a bug in Web API OData, nightly build. I believe this is a bug in `ODataEntityTypeSerializer`, but it is also likely a design flaw in EntitySetController.
In OData, it's valid to have a url like http://localhost/EntitySet(10)?$expand=Property1/SubProperty,Property2 , where 10 is an ID in EntitySet. I haven't verified that this is valid in the odata spec, but it's supported by WCF Data Services server and client. More importantly, such URLs are built by the WCF data services client if you create a query with `Expand` and with `where ID == 10`.
In `EntitySetController`, the default implementation of the Get(TKey) action calls `GetEntityByKey(key)`, then returns the entity or `null` if not found - so there's clearly no query support for executing the expand.
If I add the [Queryable] attribute:
``` C#
[Queryable(AllowedQueryOptions = AllowedQueryOptions.All, MaxExpansionDepth = 15)]
public virtual HttpResponseMessage Get([FromODataUri] TKey key)
{
TEntity entity = GetEntityByKey(key);
return EntitySetControllerHelpers.GetByKeyResponse(Request, entity);
}
```
and invoke such a query, all the link collections are empty (not surprising), and all the navigation properties/links throw an exception during serialization, like:
``` xml
<m:type>System.InvalidOperationException</m:type>
<m:stacktrace></m:stacktrace>
<m:internalexception>
<m:message>The EDM instance of type '[Property1Type Nullable=True]' is missing the property 'Title'.</m:message>
<m:type>System.InvalidOperationException</m:type>
<m:stacktrace> at System.Web.Http.OData.EntityInstanceContext.GetPropertyValue(String propertyName)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.CreateStructuralProperty(IEdmStructuralProperty structuralProperty, EntityInstanceContext entityInstanceContext)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.CreateStructuralPropertyBag(IEnumerable`1 structuralProperties, EntityInstanceContext entityInstanceContext)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.CreateEntry(SelectExpandNode selectExpandNode, EntityInstanceContext entityInstanceContext)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteEntry(Object graph, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteObjectInline(Object graph, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteExpandedNavigationProperty(KeyValuePair`2 navigationPropertyToExpand, EntityInstanceContext entityInstanceContext, ODataWriter writer)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteExpandedNavigationProperties(IDictionary`2 navigationPropertiesToExpand, EntityInstanceContext entityInstanceContext, ODataWriter writer)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteEntry(Object graph, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteObjectInline(Object graph, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteObject(Object graph, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)
at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__10.MoveNext()</m:stacktrace>
</m:internalexception>
```
Note that this exception occurs from serializing a null relationship object, and even though the object is null, the error message says the null object's property is missing. So the error is misleading, and it shouldn't happen.
Further, I can't add support for such queries in `Get(TKey)`, because I can't process `ODataQueryOptions` in the normal way. For more info on that, see https://aspnetwebstack.codeplex.com/workitem/1073
.
To be clear, I'd like to implement something like:
``` C#
public virtual HttpResponseMessage Get([FromODataUri] TKey key, ODataQueryOptions<TEntity> queryOptions)
{
queryOptions.Validate(QueryValidationSettings);
IQueryable<TEntity> query = GetBaseQueryable().Where(e => e.ID == key);
IQueryable queryOptionsApplied = queryOptions.ApplyTo(query);
TEntity entity = queryOptionsApplied.Cast<TEntity>().FirstOrDefault();
return EntitySetControllerHelpers.GetByKeyResponse(Request, entity);
}
```
- but I can't due to the bug in 1073.
In OData, it's valid to have a url like http://localhost/EntitySet(10)?$expand=Property1/SubProperty,Property2 , where 10 is an ID in EntitySet. I haven't verified that this is valid in the odata spec, but it's supported by WCF Data Services server and client. More importantly, such URLs are built by the WCF data services client if you create a query with `Expand` and with `where ID == 10`.
In `EntitySetController`, the default implementation of the Get(TKey) action calls `GetEntityByKey(key)`, then returns the entity or `null` if not found - so there's clearly no query support for executing the expand.
If I add the [Queryable] attribute:
``` C#
[Queryable(AllowedQueryOptions = AllowedQueryOptions.All, MaxExpansionDepth = 15)]
public virtual HttpResponseMessage Get([FromODataUri] TKey key)
{
TEntity entity = GetEntityByKey(key);
return EntitySetControllerHelpers.GetByKeyResponse(Request, entity);
}
```
and invoke such a query, all the link collections are empty (not surprising), and all the navigation properties/links throw an exception during serialization, like:
``` xml
<m:type>System.InvalidOperationException</m:type>
<m:stacktrace></m:stacktrace>
<m:internalexception>
<m:message>The EDM instance of type '[Property1Type Nullable=True]' is missing the property 'Title'.</m:message>
<m:type>System.InvalidOperationException</m:type>
<m:stacktrace> at System.Web.Http.OData.EntityInstanceContext.GetPropertyValue(String propertyName)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.CreateStructuralProperty(IEdmStructuralProperty structuralProperty, EntityInstanceContext entityInstanceContext)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.CreateStructuralPropertyBag(IEnumerable`1 structuralProperties, EntityInstanceContext entityInstanceContext)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.CreateEntry(SelectExpandNode selectExpandNode, EntityInstanceContext entityInstanceContext)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteEntry(Object graph, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteObjectInline(Object graph, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteExpandedNavigationProperty(KeyValuePair`2 navigationPropertyToExpand, EntityInstanceContext entityInstanceContext, ODataWriter writer)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteExpandedNavigationProperties(IDictionary`2 navigationPropertiesToExpand, EntityInstanceContext entityInstanceContext, ODataWriter writer)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteEntry(Object graph, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteObjectInline(Object graph, ODataWriter writer, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.Serialization.ODataEntityTypeSerializer.WriteObject(Object graph, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)
at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__10.MoveNext()</m:stacktrace>
</m:internalexception>
```
Note that this exception occurs from serializing a null relationship object, and even though the object is null, the error message says the null object's property is missing. So the error is misleading, and it shouldn't happen.
Further, I can't add support for such queries in `Get(TKey)`, because I can't process `ODataQueryOptions` in the normal way. For more info on that, see https://aspnetwebstack.codeplex.com/workitem/1073
.
To be clear, I'd like to implement something like:
``` C#
public virtual HttpResponseMessage Get([FromODataUri] TKey key, ODataQueryOptions<TEntity> queryOptions)
{
queryOptions.Validate(QueryValidationSettings);
IQueryable<TEntity> query = GetBaseQueryable().Where(e => e.ID == key);
IQueryable queryOptionsApplied = queryOptions.ApplyTo(query);
TEntity entity = queryOptionsApplied.Cast<TEntity>().FirstOrDefault();
return EntitySetControllerHelpers.GetByKeyResponse(Request, entity);
}
```
- but I can't due to the bug in 1073.
↧
Edited Issue: Json.Encode doesn't encode array properties returned from Json.Decode [1085]
With this code:
```
var personJson = "{name:\"George\",aliases:[\"Peter\",\"David\"]}";
var person = Json.Decode(personJson);
person.name = "Michael";
personJson = Json.Encode(person);
```
Json.Encode is currently outputting:
```
"{\"name\":\"Michael\",\"aliases\":{}}"
```
When it should be:
```
"{\"name\":\"Michael\",\"aliases\":[\"Peter\",\"David\"]}"
```
The fix is in System.Web.Helpers/DynamicJavaScriptConverter.cs
```
// Get the value for each member in the dynamic object
foreach (string memberName in memberNames)
{
- values[memberName] = DynamicHelper.GetMemberValue(obj, memberName);
+ var value = DynamicHelper.GetMemberValue(obj, memberName);
+ if (value is DynamicJsonArray)
+ value = (object[])(DynamicJsonArray)value;
+ values[memberName] = value;
}
return values;
```
```
var personJson = "{name:\"George\",aliases:[\"Peter\",\"David\"]}";
var person = Json.Decode(personJson);
person.name = "Michael";
personJson = Json.Encode(person);
```
Json.Encode is currently outputting:
```
"{\"name\":\"Michael\",\"aliases\":{}}"
```
When it should be:
```
"{\"name\":\"Michael\",\"aliases\":[\"Peter\",\"David\"]}"
```
The fix is in System.Web.Helpers/DynamicJavaScriptConverter.cs
```
// Get the value for each member in the dynamic object
foreach (string memberName in memberNames)
{
- values[memberName] = DynamicHelper.GetMemberValue(obj, memberName);
+ var value = DynamicHelper.GetMemberValue(obj, memberName);
+ if (value is DynamicJsonArray)
+ value = (object[])(DynamicJsonArray)value;
+ values[memberName] = value;
}
return values;
```
↧