Current filter:
                                You should refresh the page.
                                  • I've converted an existing OData service that inherited from XpoDataServiceV3 (WCF-based) to support OData4 with WebApi, like your sample at https://github.com/DevExpress-Examples/XPO_how-to-implement-odata4-service-with-xpo, and also extended it to use the XAF Security System like the sample posted in https://www.devexpress.com/Support/Center/Question/Details/T721161. Getting data is now working fine and the access restrictions configured in the XAF type permissions are enforced. However, if I try to update a record with the OData4 service, I'm getting an exception stating:
                                    "This error occurs because you tried to use the Session.DefaultSession (please refer to XPO documentation to learn more) in XAF. It is not allowed by default. Instead, use the XafApplication.ConnectionString property to configure the connection to the database according to your needs. Most likely, this problem is caused by the fact that you instantiated your persistent object, XPCollection, etc. without the Session parameter and the default session was used by default. Use a non-default session object to instantiate your objects and collections."

                                    I ran the customer sample from T721161 and found that this also has the same issue error when trying to update (PATCH) a Product record. However, as anticipated your unmodified sample at https://github.com/DevExpress-Examples/XPO_how-to-implement-odata4-service-with-xpo doesn't get this error if I PATCH a Product.

                                    The error message makes it pretty clear that using XpoDefault.Session won't work in the context of XAF, but the other option of XafApplication.ConnectionString doesn't seem possible either since the project doesn't have an actual XAF application; it only uses the security system as described here: https://docs.devexpress.com/eXpressAppFramework/113558/task-based-help/security/how-to-use-the-integrated-mode-of-the-security-system-in-non-xaf-applications

                                    I've attached my version of the sample from T721161 since it's much easier to test than my real application.

                                    To replicate the issue:
                                    * Set connection string in web.config to desired values for a local database.
                                    * Debug the project in VS. (NOTE that each time the app is started it will delete and re-add all the test data)
                                    * In Postman or other testing tool, set Authorization to use Basic with Username/pw both set to "admin"
                                    * Set method to PATCH
                                    * Set URL to http://localhost:54417/Products(2)
                                    * Set body to { "UnitPrice": 210.12 }
                                    * Add Content-Type header with value "application/json"
                                    * Send request
                                    * Note exception that occurs in Product.cs at that call to the parameter-less constructor "public Product() { }"

                                    Can you tell me how to solve this?

                                    Thanks,
                                    Brett

                                • Andrey K (DevExpress Support) 07.09.2019

                                  Hello,

                                  Please give us additional time to research your project and this scenario.

                                  Thanks,
                                  Andrey

                                • Brett Zook 07.15.2019

                                  Just checking to see if you have any updates on this issue.

                                  Thanks,
                                  Brett

                                • Andrey K (DevExpress Support) 07.15.2019

                                  Hello,

                                  We are preparing an answer for you. Please bear with us.

                                  Thanks,
                                  Andrey

                                1 Solution

                                Creation Date Importance Sort by

                                Hello Brett,

                                Thank you for your patience. This error occurs because the ODataResourceDeserializer class uses the parameterless constructor to create XPO objects. In this case, XPO uses the static Session.DefaultSession property, which is prohibited in XAF Data Layer (XPO Best Practices > 5. Avoid the use of a default session).

                                To avoid an exception raised when using POST and PATCH methods, it is necessary to call the XpoDefault.Session.Connect method before creating an XPObjectSpaceProvider instance. Add this code after the XpoDefault.DataLayer property assignment in the Gloabl.asax.cs file:

                                XpoDefault.DataLayer = ConnectionHelper.CreateDataLayer(AutoCreateOption.SchemaAlreadyExists, true);
                                XpoDefault.Session.Connect();
                                Show all comments
                                • Brett Zook 07.18.2019

                                  I started to apply this technique in my project and the first observation is that once I add the projection of MyService.XpoModels.Order to MyService.Models.Order using .Select() as shown in the sample above, the .AsWrappedQuery() isn't supported anymore because the .Select() returns IQueryable<MyService.Models.Order> rather than XPQuery<MyService.XpoModels.Order>.

                                  If I replace the .AsWrappedQuery() with .AsQueryable(), the project will build, but I don't know if .AsWrappedQuery() is required for proper OData function.

                                  Is there something I'm missing or that's not shown in the sample code above that makes .AsWrappedQuery() work?

                                • Brett Zook 07.19.2019

                                  Also, can you provide guidance on how to implement the Patch action in the controller when using a POCO class?

                                  As an example, I added a new XPO class to the project I initially attached called Message, that has no relationships to other entities and just 3 properties: MessageID, AuthorName, and Content. Then I created a POCO class called MessageDto that mirrors those same properties.

                                  If I use a POCO class when building the ODataModel instead of the XPO class, like:

                                  [C#]
                                  var messages = builder.EntitySet("Messages");

                                  and change nothing in the Patch action of the controller, I get this error when sending a PATCH request in Postman:

                                  [JavaScript]
                                  { "error": { "code": "", "message": "The request entity's media type 'application/json' is not supported for this resource.", "innererror": { "message": "No MediaTypeFormatter is available to read an object of type 'Delta`1' from content with media type 'application/json'.", "type": "System.Net.Http.UnsupportedMediaTypeException", "stacktrace": " at System.Net.Http.HttpContentExtensions.ReadAsAsync[T](HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)\r\n at System.Web.Http.ModelBinding.FormatterParameterBinding.ReadContentAsync(HttpRequestMessage request, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)" } } }

                                  But if I change the Patch action of the controller to look like this:

                                  [C#]
                                  [HttpPatch] public IHttpActionResult Patch([FromODataUri] int key, Delta<MessageDto> message) { if(!ModelState.IsValid) { return BadRequest(); } var result = ApiHelper.Patch<MessageDto, int>(key, message); if (result != null) { return Updated(result); } return NotFound(); }

                                  The call to ApiHelper.Patch fails because it calls uow.GetObjectByKey with the POCO type (MessageDto) which isn't in the XPO type dictionary.

                                  I have tried the approach of calling uow.GetObjectByKey with the XPO type Message instead of MessageDto, but then I'm not sure how to get the modified values contained in the Delta<MessageDto> object into the XPO Message object.

                                  Thanks,
                                  Brett

                                • Brett Zook 07.19.2019

                                  FYI, just noticed that my first code snippet in the previous comment had some text removed. It should look like this:

                                  [C#]
                                  var messages = builder.EntitySet<MessageDto>("Messages");
                                • Uriah (DevExpress Support) 07.19.2019

                                  Hello Brett,

                                  >> I started to apply this technique in my project and the first observation is that once I add the projection of MyService.XpoModels.Order to MyService.Models.Order using .Select() as shown in the sample above, the .AsWrappedQuery() isn't supported anymore because the .Select() returns IQueryable<MyService.Models.Order> rather than XPQuery<MyService.XpoModels.Order>.

                                  It is necessary to modify the method signature so that it supports the IQueryable<T> interface. This should work, because the XpoLinqQuery<T> class uses the XPQuery features indirectly through the common API (IQueryable, IQueryProvider).

                                  [C#]
                                  static class IQueryableExtensions { public static XpoLinqQuery<T> AsWrappedQuery<T>(this IQueryable<T> source, Session session) { return new XpoLinqQuery<T>(source, session); } } public class XpoLinqQuery<T> : IOrderedQueryable<T> { readonly Expression queryExpression; readonly IQueryProvider queryProvider; public XpoLinqQuery(IQueryable<T> query, Session session) : this(new XpoLinqQueryProvider(query.Provider, session), query.Expression) { } // ..

                                  It will be necessary to pass the Session parameter explicitly when calling the AsWrappedQueryt method in the Controller:

                                  [C#]
                                  return Session.Query<Product>() .Select(p => new ODataService.POCO.Products() { ProductID = p.ProductID, ProductName = p.ProductName, UnitPrice = p.UnitPrice, Picture = p.Picture }) .AsWrappedQuery(Session);

                                  I added this information to the answer.

                                  >> If I replace the .AsWrappedQuery() with .AsQueryable(), the project will build, but I don't know if .AsWrappedQuery() is required for proper OData function.

                                  The AsWrappedQuery method is required to support complex queries, such as https://myservice.com/Products?$select=ProductName.

                                  >> Also, can you provide guidance on how to implement the Patch action in the controller when using a POCO class?

                                  Yes, here is the modified ApiHelper.Patch method that accepts a POCO class as a parameter:

                                  [C#]
                                  public static TEntity Patch<TEntity, TProjection, TKey>(TKey key, Delta<TProjection> delta) where TEntity : PersistentBase where TProjection : class { using(UnitOfWork uow = ConnectionHelper.CreateSession()) { TEntity existing = uow.GetObjectByKey<TEntity>(key); if(existing != null) { object value = null; foreach (string propertyName in delta.GetChangedPropertyNames()) { if (delta.TryGetPropertyValue(propertyName, out value)) { XPMemberInfo mi = existing.ClassInfo.GetMember(propertyName); mi.SetValue(existing, value); } } uow.CommitChanges(); } return existing; } }
                                  [C#]
                                  [HttpPatch] public IHttpActionResult Patch([FromODataUri] int key, Delta<ODataService.POCO.Products> product) { if(!ModelState.IsValid) { return BadRequest(); } var result = ApiHelper.Patch<Product, ODataService.POCO.Products, int>(key, product); if(result != null) { return Updated(result); } return NotFound(); }

                                  I reviewed and tested the remaining methods, and they should work without changes. I have updated the answer, accordingly.

                                • Brett Zook 07.22.2019

                                  Thanks for these updates, we will try them out.

                                • Brett Zook 07.24.2019

                                  I've made the changes as shown above in my test project. PATCH operations work now which is great. However, I've run into a problem with OData GETs that use any of the OData query syntax.

                                  For example, http://localhost:54417/Messages works fine and returns all the Message objects.

                                  However, http://localhost:54417/Messages?$select=AuthorName fails due to an exception that occurs within XpoLinqQueryProvider.GetExpressionType():

                                  [C#]
                                  System.InvalidOperationException HResult=0x80131509 Message=Operation is not valid due to the current state of the object. Source=ODataService StackTrace: at ODataService.Helpers.XpoLinqQueryProvider.GetExpressionType(Expression expression) in C:\Source\Samples\XAF odata4-service-with-xpo and Xaf security\CS\ODataService\Helpers\XpoLinqQuery.cs:line 159 at ODataService.Helpers.XpoLinqQueryProvider.Execute(Expression expression) in C:\Source\Samples\XAF odata4-service-with-xpo and Xaf security\CS\ODataService\Helpers\XpoLinqQuery.cs:line 135 at ODataService.Helpers.XpoLinqQuery`1.System.Collections.IEnumerable.GetEnumerator() in C:\Source\Samples\XAF odata4-service-with-xpo and Xaf security\CS\ODataService\Helpers\XpoLinqQuery.cs:line 63 at Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteResourceSet(IEnumerable enumerable, IEdmTypeReference resourceSetType, ODataWriter writer, ODataSerializerContext writeContext) at Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObjectInline(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext) at Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObject(Object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) at Microsoft.AspNet.OData.Formatter.ODataOutputFormatterHelper.WriteToStream(Type type, Object value, IEdmModel model, ODataVersion version, Uri baseAddress, MediaTypeHeaderValue contentType, IWebApiUrlHelper internaUrlHelper, IWebApiRequestMessage internalRequest, IWebApiHeaders internalRequestHeaders, Func`2 getODataMessageWrapper, Func`2 getEdmTypeSerializer, Func`2 getODataPayloadSerializer, Func`1 getODataSerializerContext) at Microsoft.AspNet.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)

                                  If attached an updated copy of my test project. You should be able to run it as is and try out the http://localhost:54417/Messages?$select=AuthorName request.

                                • Andrey K (DevExpress Support) 07.25.2019

                                  Hello,

                                  We need additional time to research your example. Please bear with us.

                                  Thanks,
                                  Andrey

                                • Andrey K (DevExpress Support) 07.26.2019

                                  Hello,
                                   
                                  To get rid of this issue, change the
                                   
                                  MessagesController 'IQueryable<MessageDto> Get()' method as follows:
                                   
                                   

                                  [C#]
                                  [EnableQuery] public IQueryable<MessageDto> Get() { Session = ConnectionHelper.CreateSession(); return Session.Query<Message>() .AsWrappedQuery(Session) .Select(m => new MessageDto { MessageID = m.MessageID, AuthorName = m.AuthorName, Content = m.Content }) .AsWrappedQuery(Session); }

                                   
                                  Try this approach and let me know whether it helps. Please note that we can't guarantee that this approach will work in all scenarios. You need thoroughly test it before use.
                                   
                                  Thanks,
                                  Andrey

                                • Brett Zook 08.06.2019

                                  That change fixed several scenarios like $filter, $count, and $select. However, errors still occur when trying to use $expand to include related objects in the response.

                                  I've attached a new copy of the project which has some refactoring. There's a new XPO type called Author (and corresponding POCO AuthorDto), that's linked to the the Message type in a one-to-many fashion.

                                  If I request http://localhost:54417/Messages?$expand=Author, I get the following exception:

                                  [C#]
                                  Complex data selection is not supported. Perhaps you are using a complex subquery in selector. at DevExpress.Xpo.XPQueryBase.ProcessAssignmentMember(CriteriaOperator property, CriteriaOperatorCollection props, CriteriaOperatorCollection correspondingPropDict) at DevExpress.Xpo.XPQueryBase.GetData(Type type) at DevExpress.Xpo.XPQueryBase.Enumerate(Type type) at DevExpress.Xpo.XPQuery`1.GetEnumerator() at DevExpress.Xpo.XPQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator() at DevExpress.Xpo.Helpers.EnumerableWrapper`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator() at System.Linq.EnumerableQuery`1.GetEnumerator() at System.Linq.EnumerableQuery`1.System.Collections.IEnumerable.GetEnumerator() at ODataService.Helpers.XpoLinqQuery`1.System.Collections.IEnumerable.GetEnumerator() in C:\Source\Samples\XAF odata4-service-with-xpo and Xaf security\CS\ODataService\Helpers\XpoLinqQuery.cs:line 63 at Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteResourceSet(IEnumerable enumerable, IEdmTypeReference resourceSetType, ODataWriter writer, ODataSerializerContext writeContext) at Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObjectInline(Object graph, IEdmTypeReference expectedType, ODataWriter writer, ODataSerializerContext writeContext) at Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceSetSerializer.WriteObject(Object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext) at Microsoft.AspNet.OData.Formatter.ODataOutputFormatterHelper.WriteToStream(Type type, Object value, IEdmModel model, ODataVersion version, Uri baseAddress, MediaTypeHeaderValue contentType, IWebApiUrlHelper internaUrlHelper, IWebApiRequestMessage internalRequest, IWebApiHeaders internalRequestHeaders, Func`2 getODataMessageWrapper, Func`2 getEdmTypeSerializer, Func`2 getODataPayloadSerializer, Func`1 getODataSerializerContext) at Microsoft.AspNet.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)

                                  Testing with some of the other types that were already in the sample and are not secured with XAF security (and therefore don't need the POCO classes layered on top) do allow the use of $expand. For example: http://localhost:54417/OrderDetails?$expand=Order

                                  Are there changes that can be made to support $expand when POCO classes are used?

                                • Uriah (DevExpress Support) 08.08.2019

                                  Hello Brett,

                                  It turned out that the POCO approach requires significant efforts to make it work in all possible scenarios. I have recently managed to make your original project work with XPO classes. To avoid an exception raised when using POST and PATCH methods, it is necessary to call the XpoDefault.Session.Connect method before creating an XPObjectSpaceProvider instance.

                                  I changed the code in the Global.asax.cs file and all operations started to work in your original project:

                                  protected void Application_Start()
                                  {
                                      GlobalConfiguration.Configuration.Services.Replace(typeof(IBodyModelValidator), new CustomBodyModelValidator());
                                      GlobalConfiguration.Configure(WebApiConfig.Register);
                                      ConnectionHelper.InitializeXafTypesInfo();
                                      ConnectionHelper.EnsureDatabaseCreated();
                                      XpoDefault.DataLayer = ConnectionHelper.CreateDataLayer(AutoCreateOption.SchemaAlreadyExists, true);
                                      XpoDefault.Session.Connect();
                                      DemoDataHelper.CleanupDatabase();
                                      DemoDataHelper.CreateDemoData();
                                  }

                                  This solution seems to be simpler and more reliable than the POCO solution. Please try it.

                                • Brett Zook 08.08.2019

                                  Wow that is much easier! So far it's working fine. Thank you.

                                • Uriah (DevExpress Support) 08.08.2019

                                  You are welcome, Brett! I have changed the subject heading and updated the answer accordingly.