The LogLevel section in the appsettings.json file does not affect the .NET Framework tracing mechanism, which is used by XPO to log the queries, still we have a few work arounds for a Netcore app
We can implement our own logger as shown here https://docs.devexpress.com/XPO/403928/best-practices/how-to-log-sql-queries#xpo-logger-net
We can set the value of the logging switch by reflection using the following snippet
If you are running Xaf Blazor in ubuntu 18.04 you might have seen the following exception
The type initializer for ‘Gdip’ threw an exception.
at DevExpress.ExpressApp.Actions.ActionBase.OnHandleException(Exception e) at DevExpress.ExpressApp.Actions.ActionBase.ExecuteCore(Delegate handler, ActionBaseEventArgs eventArgs) at DevExpress.ExpressApp.Actions.PopupWindowShowAction.DoExecute(Window window) at DevExpress.ExpressApp.Actions.PopupWindowShowAction.DialogController_Accepting(Object sender, DialogControllerAcceptingEventArgs e) at DevExpress.ExpressApp.SystemModule.DialogController.Accept(SimpleActionExecuteEventArgs args) at DevExpress.ExpressApp.SystemModule.DialogController.acceptAction_OnExecute(Object sender, SimpleActionExecuteEventArgs e) at DevExpress.ExpressApp.Actions.SimpleAction.RaiseExecute(ActionBaseEventArgs eventArgs) at DevExpress.ExpressApp.Actions.ActionBase.ExecuteCore(Delegate handler, ActionBaseEventArgs eventArgs)
The error is caused by missing dependency, so the DotNet runtime itself will throw that exception. Also, I want to highlight that the exception is not related to XAF, you can read more about this problem here https://github.com/dotnet/runtime/issues/27200
To get the missing dependency just open a console and run the following commands
I have been using XPO from DevExpress since day one. For me is the best O.R.M in the dot net world, so when I got the news that XPO was going to be free of charge I was really happy because that means I can use it in every project without adding cost for my customers.
Nowadays all my customer needs some type of mobile development, so I have decided to master the combination of XPO and Xamarin
Now there is a problem when using XPO and Xamarin and that is the network topology, database connections are no designed for WAN networks.
Let’s take MS SQL server as an example, here are the supported communication protocols
TCP/IP.
Named Pipes
To quote what Microsoft web site said about using the protocols above in a WAN network
In a fast-local area network (LAN) environment, Transmission Control Protocol/Internet Protocol (TCP/IP) Sockets and Named Pipes clients are comparable with regard to performance. However, the performance difference between the TCP/IP Sockets and Named Pipes clients becomes apparent with slower networks, such as across wide area networks (WANs) or dial-up networks. This is because of the different ways the interprocess communication (IPC) mechanisms communicate between peers.”
So, what other options do we have? Well if you are using the full DotNet framework you can use WCF.
So, it looks like WCF is the solution here since is mature and robust communication framework but there is a problem, the implementation of WCF for mono touch (Xamarin iOS) and mono droid (Xamarin Android)
You can read about Xamarin limitations in the following links
I don’t want to go into details about how the limitation of each platform affects XPO and WCF but basically the main limitation is the ability to use reflection and emit new code which is needed to generate the WCF client, also in WCF there are problems in the serialization behaviors.
So basically, what we need to do is to replace the WCF layer with some other technology to communicate to the database server
The technology I’ve selected for this AspNetCore which I would say is a really nice technology that is modern, multi-platform and easy to use. Here below you can see what is the architecture of the solution
AspNetCore
Rest API
So, what we need basically is to be able to communicate the data layer with the data store through a network architecture.
The network architecture that I have chosen is a rest API which is one of the strong fronts of AspNetCore. The rest API will work as the server that forward the communication from XPO to the Database and vice versa, you can find a project template of the server implementation here https://www.jocheojeda.com/download/560/ this implementation references one nuget where I have written the communication code, you can fine the nuget here https://nuget.bitframeworks.com/feeds/main/BIT.Xpo.AgnosticDataStore.Server/19.1.5.1
For a while now I have been trying to create a framework for XAF (yes I know XAF is already a framework) but it was too difficult to handle the different version of XAF and upgrade the solution from version to version.
Luckily for me, DevExpress team decided to publish all their DLLs as nuget packages, you can learn more about that here. But there was still one problem for me, at that time they did not include the nugets for XAF, later that year (2018) they decided to publish the nugets for XAF, you can read about it here
Now I have all the pieces to create the project template for the modules of my framework, at least that is what I thought, there was still one more stone in my path and it was the csproj file. At this moment (version 18.2.x) XAF project templates are based on visual studio 2015 project format, so the way the projects handles nuget references is based on the old standard packages.config, another problem is that if you want to package your module as a nuget you have to use the old package.nuspec.
So let’s migrate our XAF module project to the new version of csproj, but first take a look to the old version of the file in the image below
Once you have a XAF solution open on visual studio these are the steps to do the migration
1) Right click on your module file and select “Unload Project”
2) Now that the project us unloaded it will appear unavailable in the solution explorer, so we can right click over it and select “edit”
4) Delete all the content of your csproj and replace it with this XML, you can also change the version of nuget files, in this case, I’m using 18.2.6
Sometimes we need to have clientside events and handle them on the server side code behind, that in a simple asp.net web page is really easy, you just have to execute a javascript that executes an HTTP request to the server. Now the question is, how do we do that in XAF?
Well, the concept is basically the same but you need to know XAF architecture the problem is that most of the code needed is not documented, but after a while, I manage to figure it out, so let’s get started.
1) Create a XAF web application
2) On your web module add a view controller
3) Implement the interface IXafCallbackHandler on the controller you just added in step 2, this is the method that will be called as a callback from javascript. This interface is not documented on the DevExpress website
4) In your view controller add a property to access XafCallbackManager
5) Override the OnViewControlsCreated method and register your callback, in this example, the name of the callback is “MyScript”
6) Now add a simple action and wire the execute event, on the execute event cast the frame as a web window and register a startup script. The code surrounded with the blue line is the javascript that triggers the callback in the callback manager, the code surrounded with red is the id if the script that we are listening for, it should match the name of the script registered on the handler in the previous step.
To execute the callback somewhere in your javascript you have to execute the following function RaiseXafCallback, this function is not documented on the DevExpress website
7) Run your application and execute the simple action added in step 6, when the javascript finish executing, the method you implemented on step 3 will be executed.
The code for this article is here the full example is in GitHub
Sometimes you are asked by a customer to implement a functionality that you have already created in the past from someone else.
In this case, a customer and dear friend from Italy asked me “Jose, can we make the XAF filter case insensitive?”
And my answer was yes, I have done in the past for a customer in El Salvador. At that time the approach we used was to save everything in uppercases and do all the searches in uppercases also
I didn’t like that approach at all, so it was time get creative again.
As always I start my research by spending some time reading tickets at https://www.devexpress.com/Support/Center/ which is the greatest source of information for all things related to DevExpress
My first idea was to edit the criteria that were sent to the collection source that means that I need a way to get the values of each node on the criteria, so I can edit them one by one. After some more research, this is what I have found
this approach seems perfect to get the properties and values of each node till test it and find out this
Michael (DevExpress Support)6 years ago
It is possible to write a hierarchy walker or visitor, but only for a specific subset of criteria operators.
What Michael means is that only you can only get the values of simple criteria and by simple, I mean the ones that do not involve functions. So, I was set back to step one again.
So, I’m back to step one but let’s see what I’ve learned from the other tickets
I know the exact moment when the criteria are evaluated
I know the criteria that are evaluated and how many nodes it contains
I know which the possible properties are involved in the creation of the criteria
Now this is the flow that I will use to make the search case insensitive
Locate the controller where the search action lives, for that, I can use the property frame and the method get controller
Interrupt the search action, for that, I will wire the event execute of the FilterController FullTextFilterAction action
Find out what are the possible properties that I can use to build the search criteria for that I will use GetFullTextSearchProperties
Build the new criteria, this is somehow tricky because the properties shown in the list view might represent complex objects and that is shown in the column is either the object default property or any other property that can be deep inside the properties tree. Also this step involve find out what is the type of the value being displayed on the list view, if the value is a string I will evaluate its value converted to upper cases using the function upper from the criteria language, if the search value is not a string but a number I will try to cast it to the type of the display column, if the cast is not invalid I will evaluate the search value as a number
Set the new filter to the list view
You can cross reference the points above with the sample code below
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using DevExpress.Data.Filtering;
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Actions;
using DevExpress.ExpressApp.Editors;
using DevExpress.ExpressApp.Layout;
using DevExpress.ExpressApp.Model;
using DevExpress.ExpressApp.Model.NodeGenerators;
using DevExpress.ExpressApp.SystemModule;
using DevExpress.ExpressApp.Templates;
using DevExpress.ExpressApp.Utils;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.Validation;
public class FilterControllerCaseInsensitive : ViewController<ListView>
{
public FilterControllerCaseInsensitive()
{
}
FilterController standardFilterController;
protected override void OnActivated()
{
standardFilterController = Frame.GetController<FilterController>();
if (standardFilterController == null)
return;
//we should wire the execution of the filter
standardFilterController.FullTextFilterAction.Execute += FullTextFilterAction_Execute;
}
protected override void OnDeactivated()
{
base.OnDeactivated();
if (standardFilterController == null)
return;
//we should unwire the execution of the filter
standardFilterController.FullTextFilterAction.Execute -= FullTextFilterAction_Execute;
}
private void FullTextFilterAction_Execute(object sender, ParametrizedActionExecuteEventArgs e)
{
//we locate filter with the key FilterController.FullTextSearchCriteriaName then we convert it to case insensitive
if (!string.IsNullOrEmpty(e.ParameterCurrentValue as string) && View.CollectionSource.Criteria.ContainsKey(FilterController.FullTextSearchCriteriaName))
View.CollectionSource.Criteria[FilterController.FullTextSearchCriteriaName] = GetCaseInsensitiveCriteria(e.ParameterCurrentValue, View.CollectionSource.Criteria[FilterController.FullTextSearchCriteriaName]);
}
private CriteriaOperator GetCaseInsensitiveCriteria(object searchValue, CriteriaOperator initialCriteria)
{
//we get a list of all the properties that can be involved in the filter
var SearchProperties = standardFilterController.GetFullTextSearchProperties();
//we declare a model class and a property name,the values on this variables will change if we property involve is a navigation property (another persistent object)
IModelClass ModelClass = null;
string PropertyName = string.Empty;
//we declare a list of operators to contains new operators we are going to create
List<CriteriaOperator> Operator = new List<CriteriaOperator>();
//we iterate all the properties
foreach (var CurrentProperty in SearchProperties)
{
//here we split the name with a dot, if length is greater than 1 it means its a navigation properties, beware that this may fail with a deep tree of properties like category.subcategory.categoryname
var Split = CurrentProperty.Split('.');
if (Split.Length > 1)
{
Debug.WriteLine(string.Format("{0}","its a complex property"));
var CurrentClass = this.View.Model.ModelClass;
for (int i = 0; i < Split.Length; i++)
{
//if its a navigation property we locate the type in the BOModel
IModelMember member = CurrentClass.OwnMembers.Where(m => m.Name == Split[i]).FirstOrDefault();
//then we set the model class and property name to the values of the navigation property like category.name where category is the model class and name is the property
CurrentClass = this.Application.Model.BOModel.GetClass(member.Type);
if (CurrentClass == null)
continue;
ModelClass = CurrentClass;
PropertyName = Split[i + 1];
}
Debug.WriteLine(string.Format("{0}:{1}", "ModelClass", ModelClass.Name));
Debug.WriteLine(string.Format("{0}:{1}", "PropertyName", PropertyName));
}
else
{
//else the model class will be the current class where the filter is executing, and the property will be the current property we are evaluating
ModelClass = this.View.Model.ModelClass;
PropertyName = CurrentProperty;
}
//we look for the property on the class model own member
var Property = ModelClass.OwnMembers.Where(m => m.Name == PropertyName).FirstOrDefault();
if (Property != null)
{
//if the property is a string it means that we can set it to upper case
if (Property.Type == typeof(string))
{
searchValue = searchValue.ToString().ToUpper();
//we create an operator where we set the value of the property to upper before we compare it, also we change the comparison value to upper
CriteriaOperator Operand = CriteriaOperator.Parse("Contains(Upper(" + CurrentProperty + "), ?)", searchValue);
//we added to the list of operators that will concatenate with OR
Operator.Add(Operand);
}
else
{
//if the property is not a string we need to try to cast the value to the correct type so we do a catch try, if we manage to cast the value it will be added to the operators list
try
{
var ConvertedType = Convert.ChangeType(searchValue, Property.Type);
CriteriaOperator operand = new BinaryOperator(CurrentProperty, ConvertedType, BinaryOperatorType.Equal);
Operator.Add(operand);
}
catch (Exception)
{
//silent exception, this will happen if the casting was not successful so we won't add the operand on this case
}
}
}
}
//we concatenate everything with an OR
var alloperators = CriteriaOperator.Or(Operator.ToArray());
Debug.WriteLine(string.Format("{0}:{1}", "alloperators", alloperators));
return alloperators;
}
}