Current filter:
                                You should refresh the page.
                                  • Description:
                                    When my views execute a POST action to update the model, XPO creates a new record in the database rather than updates the editing record. Is it a bug? How to resolve this issue? What is the best practice of using XPO in an ASP.NET MVC application?

                                    Answer:
                                    The main difficulty of integrating the eXpress Persistent Objects framework into the ASP.NET MVC application is that both these frameworks have their own approaches to create a model instance. ASP.NET MVC requires a model to provide the parameterless constructor. It simply creates a new model instance each time it is required. XPO considers every object created via public constructors as new and inserts a new record in the database associated with this object. Hence, it is necessary to prevent the ASP.NET MVC engine from creating new instances and address this task manually. We will describe two possible approaches that can be used to address this issue.
                                    Approach #1
                                    Create a custom model binder inherited from the DefaultModelBinder (or DevExpressEditorsBinder if DevExpress ASP.NET components are used). This binder will be used to create a new model when it is applied to the POST method parameters.
                                    Nice, but it is not so simple. What about the Session? To avoid mixing persistent objects loaded into different Sessions, it is better to create the Session within the Controller class and pass it to a custom binder. The controller instance is passed as a parameter to the ModelBinder.CreateModel method. All we need is to declare the interface that can be used to obtain a Session instance and to identify our custom Controller.

                                    [C#]
                                    public interface IXpoController { Session XpoSession { get; } }
                                    [VB.NET]
                                    Public Interface IXpoController ReadOnly Property XpoSession() As Session End Interface

                                    And the Controller:

                                    [C#]
                                    public class BaseXpoController : Controller, IXpoController { public BaseXpoController() { XpoSession = CreateSession(); } Session fXpoSession; public Session XpoSession { get { return fXpoSession; } private set { fXpoSession = value; } } protected virtual Session CreateSession() { return XpoHelper.GetNewSession(); } }
                                    [VB.NET]
                                    Public Class BaseXpoController Inherits Controller Implements IXpoController Public Sub New() XpoSession = CreateSession() End Sub Private fXpoSession As Session Public Property XpoSession() As Session Get Return fXpoSession End Get Private Set(ByVal value As Session) fXpoSession = value End Set End Property Protected Overridable Function CreateSession() As Session Return XpoHelper.GetNewSession() End Function End Class

                                    The controller uses the XpoHelper class described in the How to use XPO in an ASP.NET (Web) application Knowledge Base article to create a Session instance.
                                    This is the time to create a custom model binder. Override only one method: CreateModel. Please see the code below. This method obtains an instance implementing the IXpoController interface from the first parameter (the ControllerContext instance). If it fails, the method throws an exception. Once we have the Session instance, the rest is technical stuff. Using the Session.GetClassInfo method, obtain metadata from the last parameter (modelType) and the value of the key property and load a persistent object via the Session.GetObjectByKey method. If a corresponding record is not found in the database, create a new persistent object via the XPClassInfo.CreateNewObbject method.

                                    [C#]
                                    public class XpoModelBinder :DevExpressEditorsBinder { protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { IXpoController xpoController = controllerContext.Controller as IXpoController; if (xpoController == null) throw new InvalidOperationException("The controller does not support IXpoController interface"); XPClassInfo classInfo = xpoController.XpoSession.GetClassInfo(modelType); ValueProviderResult result = bindingContext.ValueProvider.GetValue(classInfo.KeyProperty.Name); return result == null ? classInfo.CreateNewObject(xpoController.XpoSession) : xpoController.XpoSession.GetObjectByKey(classInfo, result.ConvertTo(classInfo.KeyProperty.MemberType)); } }
                                    [VB.NET]
                                    Public Class XpoModelBinder Inherits DevExpressEditorsBinder Protected Overrides Function CreateModel(ByVal controllerContext As ControllerContext, ByVal bindingContext As ModelBindingContext, ByVal modelType As Type) As Object Dim xpoController As IXpoController = TryCast(controllerContext.Controller, IXpoController) If xpoController Is Nothing Then Throw New InvalidOperationException("The controller does not support IXpoController interface") End If Dim classInfo As XPClassInfo = xpoController.XpoSession.GetClassInfo(modelType) Dim result As ValueProviderResult = bindingContext.ValueProvider.GetValue(classInfo.KeyProperty.Name) Return If(result Is Nothing, classInfo.CreateNewObject(xpoController.XpoSession), xpoController.XpoSession.GetObjectByKey(classInfo, result.ConvertTo(classInfo.KeyProperty.MemberType))) End Function End Class

                                    That is all. Now the custom model binder can be used in the application as shown below:

                                    [C#]
                                    [AcceptVerbs(HttpVerbs.Post)] public ActionResult Create([ModelBinder(typeof(XpoModelBinder))]T newEntity) { return SaveModel(newEntity); }
                                    [VB.NET]
                                    <AcceptVerbs(HttpVerbs.Post)> _ Public Function Create(<ModelBinder(GetType(XpoModelBinder))> ByVal newEntity As T) As ActionResult Return SaveModel(newEntity) End Function

                                    Approach #2
                                    The weakness of the previous approach is that it does not properly handle all scenarios. For example, it does not support binding of the ComboBox column to the property that references another persistent object. We offer you a completely different approach: do not bind persistent objects directly to views, but use intermediate ViewModel classes. In this example ViewModel is a simple DTO class. The logic should be concentrated in the Controller.
                                    This approach provides more benefits. Data will be uncoupled from persistent objects, developers will achieve the full control over persistent objects and Sessions because the ASP.NET MCV engine will work with ViewModels. It is also possible to decrease the amount of data loaded from the SQL server using LINQ to XPO and explicitly specify which properties to load. Previously this approach could be applied only to read-only views because of the use of anonymous types. Now there is no necessity to load the entire persistent class since ViewModel properties can be populated with values from the LINQ query result.

                                    [C#]
                                    IEnumerable<CustomerViewModel> GetCustomers() { return (from c in XpoSession.Query<Customer>().ToList() select new CustomerViewModel() { ID = c.Oid, Name = c.Name }).ToList(); }
                                    [VB.NET]
                                    Private Function GetCustomers() As IEnumerable(Of CustomerViewModel) Return ( _ From c In XpoSession.Query(Of Customer)().ToList() _ Select New CustomerViewModel() With {.ID = c.Oid, .Name = c.Name}).ToList() End Function

                                    The BaseViewModel class is very simple:

                                    [C#]
                                    using DevExpress.Xpo; public abstract class BaseViewModel<T> { int id = -1; public int ID { get { return id; } set { id = value; } } public abstract void GetData(T model); }
                                    [VB.NET]
                                    Imports DevExpress.Xpo Public MustInherit Class BaseViewModel(Of T) Private id_Renamed As Integer = -1 Public Property ID() As Integer Get Return id_Renamed End Get Set(ByVal value As Integer) id_Renamed = value End Set End Property Public MustOverride Sub GetData(ByVal model As T) End Class

                                    BaseViewModel is defined as a generic class. This way, it is possible to declare abstract members that will use the generic parameter. In descendant classes the generic parameter will be replaced with the actual type that allows developers to access model properties at compile time. Here is a simple ViewModel class inherited from BaseViewModel and based on the Customer persistent class:

                                    [C#]
                                    public class CustomerViewModel : BaseViewModel<Customer> { public string Name { get; set; } public override void GetData(Customer model) { model.Name = Name; } }
                                    [VB.NET]
                                    Public Class CustomerViewModel Inherits BaseViewModel(Of Customer) Private privateName As String Public Property Name() As String Get Return privateName End Get Set(ByVal value As String) privateName = value End Set End Property Public Overrides Sub GetData(ByVal model As Customer) model.Name = Name End Sub End Class

                                    The GetData method will be used by the Controller to update persistent object properties. Below you can see the implementation of the base Controller class, which is also generic.

                                    [C#]
                                    using System.Web.Mvc; using DevExpress.Xpo; using DevExpress.Xpo.DB.Exceptions; namespace DevExpressMvcApplication.Controllers { public abstract class BaseXpoController<T> :Controller where T:XPObject { UnitOfWork fSession; public BaseXpoController() : base() { fSession = CreateSession(); } protected UnitOfWork XpoSession { get { return fSession; } } protected virtual UnitOfWork CreateSession() { return XpoHelper.GetNewUnitOfWork(); } bool Save(BaseViewModel<T> viewModel, bool delete) { T model = XpoSession.GetObjectByKey<T>(viewModel.ID); if (model == null && !delete) model = (T)XpoSession.GetClassInfo<T>().CreateNewObject(XpoSession); if (!delete) viewModel.GetData(model); else if (model != null) XpoSession.Delete(model); try { XpoSession.CommitChanges(); return true; } catch (LockingException) { return false; } } protected bool Save(BaseViewModel<T> viewModel) { return Save(viewModel, false); } protected bool Delete(BaseViewModel<T> viewModel) { return Save(viewModel, true); } } }
                                    [VB.NET]
                                    Imports System.Web.Mvc Imports DevExpress.Xpo Imports DevExpress.Xpo.DB.Exceptions Namespace DevExpressMvcApplication.Controllers Public MustInherit Class BaseXpoController(Of T As XPObject) Inherits Controller Private fSession As UnitOfWork Public Sub New() MyBase.New() fSession = CreateSession() End Sub Protected ReadOnly Property XpoSession() As UnitOfWork Get Return fSession End Get End Property Protected Overridable Function CreateSession() As UnitOfWork Return XpoHelper.GetNewUnitOfWork() End Function Private Function Save(ByVal viewModel As BaseViewModel(Of T), ByVal delete As Boolean) As Boolean Dim model As T = XpoSession.GetObjectByKey(Of T)(viewModel.ID) If model Is Nothing AndAlso (Not delete) Then model = CType(XpoSession.GetClassInfo(Of T)().CreateNewObject(XpoSession), T) End If If (Not delete) Then viewModel.GetData(model) ElseIf model IsNot Nothing Then XpoSession.Delete(model) End If Try XpoSession.CommitChanges() Return True Catch e1 As LockingException Return False End Try End Function Protected Function Save(ByVal viewModel As BaseViewModel(Of T)) As Boolean Return Save(viewModel, False) End Function Protected Function Delete(ByVal viewModel As BaseViewModel(Of T)) As Boolean Return Save(viewModel, True) End Function End Class End Namespace

                                    This class encapsulates the Save and Delete methods to avoid code duplication. These methods will be reused in descendant classes:

                                    [C#]
                                    using System; using System.Linq; using System.Web.Mvc; using DevExpress.Xpo; using DevExpress.Web.Mvc; using System.Collections.Generic; namespace DevExpressMvcApplication.Controllers { public class CustomersController : BaseXpoController<Customer> { public ActionResult Index() { return View(GetCustomers()); } public ActionResult IndexPartial() { return PartialView("IndexPartial", GetCustomers()); } [HttpPost] public ActionResult EditCustomer([ModelBinder(typeof(DevExpressEditorsBinder))] CustomerViewModel customer) { Save(customer); return PartialView("IndexPartial", GetCustomers()); } [HttpPost] public ActionResult DeleteCustomer([ModelBinder(typeof(DevExpressEditorsBinder))] CustomerViewModel customer) { Delete(customer); return PartialView("IndexPartial", GetCustomers()); } IEnumerable<CustomerViewModel> GetCustomers() { return (from c in XpoSession.Query<Customer>().ToList() select new CustomerViewModel() { ID = c.Oid, Name = c.Name }).ToList(); } } }
                                    [VB.NET]
                                    Imports System Imports System.Linq Imports System.Web.Mvc Imports DevExpress.Xpo Imports DevExpress.Web.Mvc Imports System.Collections.Generic Namespace DevExpressMvcApplication.Controllers Public Class CustomersController Inherits BaseXpoController(Of Customer) Public Function Index() As ActionResult Return View(GetCustomers()) End Function Public Function IndexPartial() As ActionResult Return PartialView("IndexPartial", GetCustomers()) End Function <HttpPost> _ Public Function EditCustomer(<ModelBinder(GetType(DevExpressEditorsBinder))> ByVal customer As CustomerViewModel) As ActionResult Save(customer) Return PartialView("IndexPartial", GetCustomers()) End Function <HttpPost> _ Public Function DeleteCustomer(<ModelBinder(GetType(DevExpressEditorsBinder))> ByVal customer As CustomerViewModel) As ActionResult Delete(customer) Return PartialView("IndexPartial", GetCustomers()) End Function Private Function GetCustomers() As IEnumerable(Of CustomerViewModel) Return ( _ From c In XpoSession.Query(Of Customer)().ToList() _ Select New CustomerViewModel() With {.ID = c.Oid, .Name = c.Name}).ToList() End Function End Class End Namespace

                                    The Save and Delete methods return the Boolean value to indicate whether or not the operation succeeds. These methods return false if end-users update the record that is already updated by another end-user. This allows programmers to notify end-users about such collisions.
                                    Also, this XPO Controller uses the UnitOfWork instead of the Session. We do not recommend you use the UnitOfWork in ASP.NET applications because in older ASP.NET applications it was unnecessary and even inconvenient. In ASP.NET MVC applications there is no reason to avoid the use of the UnitOfWork.
                                    See also: How to use XPO in an ASP.NET (Web) application

                                • Jurgen Ramirez 11.19.2013

                                  Hello! I implemented the first solution but because I have no control over the session because every time I call XpoHelper.GetNewSession (), I create a session and then do not save a change to an object.
                                  it's my code:
                                         public ActionResult Edit(int id)
                                         {
                                             Manager manager= XpoHelper.GetNewSession().GetObjectByKey<Manager>(id);
                                             return View(manager);
                                         }
                                         [HttpPost]
                                         public ActionResult Edit([ModelBinder(typeof(Prueba002.Models.XpoModelBinder))]Mannager model)
                                         {
                                               if (model.IsChanged)
                                                {
                                                       model.Save();
                                                       XpoHelper.GetNewUnitOfWork().CommitChanges();
                                                      return RedirectToAction("Index");
                                               }
                                             return view(model);
                                         }

                                • Kate Dehbashi (DevExpress) 11.19.2013

                                  Hi Jurgen,
                                  I created a separate thread for your question. Please refer to the following link for further correspondence on this subject:
                                  use XPO in an ASP.NET MVC application

                                • chris petchey @ bluerock 12.25.2017

                                  I've used solution #1 satisfactorily except that the last line of the XpoModelBinder fails

                                  xpoController.XpoSession.GetObjectByKey(classInfo, result.ConvertTo(classInfo.KeyProperty.MemberType))My Xpo object is a simple XpoObject class, so has "Oid" as its key and "Name" as a string property.
                                  When I edit the record with Oid=1, the "result" variable returned here:
                                  ValueProviderResult result = bindingContext.ValueProvider.GetValue(classInfo.KeyProperty.Name);has a "AttemptedValue" property = ""1""
                                  When the result.ConvertTo function tries to convert this value to an "int" it fails.
                                  Is the GridView returning an incorrect type value to the "result" variable?

                                • Uriah (DevExpress Support) 12.25.2017

                                  Hello Chris,

                                  I've created a separate ticket on your behalf (T590762: Binding context returns value in an unexpected format when binding editors to an XPO model). It has been placed in our processing queue and will be answered shortly.

                                0 Solutions

                                Creation Date Importance Sort by