Partial platform specific methods in .NET MAUI - .net-maui

I'm trying to implement plattform specific partial method in .NET MAUI to get the connection string for the database.
In the "main application":
namespace TestApp.DL;
public partial class BaseHandler
{
public partial string GetDBPath();
private string GetCnnPath()
{
var dbPath = GetDBPath();
var cnnPath = $"Data Source={dbPath}";
return cnnPath;
}
...
}
in the platform folders in the project:
where each contain the plattform specific implementation:
namespace TestApp.DL;
// All the code in this file is only included on Android.
public partial class BaseHandler
{
public string GetDBPath()
{
var dbName = "com.mycompany.mydatabase.db";
return Android.App.Application.Context.GetDatabasePath(dbName).AbsolutePath;
}
}
...but I keep getting "Error CS8795: Partial method 'BaseHandler.GetDBPath()' must have an implementation part because it has accessibility modifiers. (CS8795)". It seems like the platform specific files are not seen by the compiler? Note, they are in a separate assembly project from the main application but that should be ok I guess, given that the fwk created the folders for me?

When you struggle with partials you can keep using partial classes, but avoid using partial methods. This is especially true when creating maui libs, were this approach tends to break, while in maui apps the compilation works fine.
The "quick fix solution", all partial classes must use same namespace obviously:
Shared code, you would want to change NET6_0 to NET7_0 whatever you are using:
public partial class BaseHandler
{
private string GetCnnPath()
{
var dbPath = GetDBPath();
var cnnPath = $"Data Source={dbPath}";
return cnnPath;
}
#if (NET6_0 && !ANDROID && !IOS && !MACCATALYST && !WINDOWS && !TIZEN)
public string GetDBPath()
{
throw new PlatformNotSupportedException();
}
#endif
}
Your platform specific code in platform Platforms/Android:
public partial class BaseHandler
{
public string GetDBPath()
{
var dbName = "com.mycompany.mydatabase.db";
return Android.App.Application.Context.GetDatabasePath(dbName).AbsolutePath;
}
}

First of all, your implementation must use the partial keyword as well:
namespace TestApp.DL;
// All the code in this file is only included on Android.
public partial class BaseHandler
{
public partial string GetDBPath()
{
var dbName = "com.mycompany.mydatabase.db";
return Android.App.Application.Context.GetDatabasePath(dbName).AbsolutePath;
}
}
Then, you should make sure that you're following the guidelines for multi-targeting: https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/configure-multi-targeting#configure-folder-based-multi-targeting
You'll need to update your .csproj file with the following:
<!-- Android -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net6.0-android')) != true">
<Compile Remove="**\Android\**\*.cs" />
<None Include="**\Android\**\*.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<!-- iOS -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net6.0-ios')) != true">
<Compile Remove="**\iOS\**\*.cs" />
<None Include="**\iOS\**\*.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<!-- Mac Catalyst -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net6.0-maccatalyst')) != true">
<Compile Remove="**\MacCatalyst\**\*.cs" />
<None Include="**\MacCatalyst\**\*.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<!-- Windows -->
<ItemGroup Condition="$(TargetFramework.Contains('-windows')) != true">
<Compile Remove="**\Windows\**\*.cs" />
<None Include="**\Windows\**\*.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
Without this, you would see another compiler error because you can only provide one body for any partial method declaration. You need to provide platform specific implementations for each platform and disable the compilation of the ones that are not needed or you can add a default implementation, but then you need file-based multi-targeting instead of platform-based multi-targeting (or a combination of both).
I've had a similar problem already and solved it here: MAUI: How to use partial classes for platform specific implementations together with net7.0 as TargetFramework in the SingleProject?

Related

Xamarin.iOS Custom View with class file VS2019 Version 16.9

Can anyone please guide me how to create a custom view in Xamarin.iOS using XCode.
Im trying to create a Custom Circular View with the class file. I was able to create a XIB file but not able to create a class file.
To continue on what you did, do the following.
Create an empty class and name it CircularView
public partial class CircularView : UIView
{
public static readonly NSString Key = new NSString("CircularView");
public static readonly UINib Nib;
static CircularView()
{
Nib = UINib.FromName("CircularView", NSBundle.MainBundle);
}
protected CircularView(IntPtr handle) : base(handle)
{
// Note: this .ctor should not contain any initialization logic.
}
public static CircularView CreateView()
{
return (CircularView)Nib.Instantiate(null, null)[0];
}
}
Create another class and name it CircularView.designer.cs
[Register ("CircularView")]
partial class CircularView
{
void ReleaseDesignerOutlets ()
{
}
}
Edit project file and add DependUpon tags like below
BEFORE
<Compile Include="Controls\CircularView.cs" />
<Compile Include="Controls\CircularView.designer.cs" />
AFTER
<Compile Include="Controls\CircularView.cs" />
<Compile Include="Controls\CircularView.designer.cs" >
<DependentUpon>CircularView.cs</DependentUpon>
</Compile>
this will ensure that VS shows the designer file as a child of CircularView.cs
where you want to use the new Custom view do the following
var v = CircularView.CreateView();
vwCustom.Add(v);
where vwCustom is a normal UIView added from Designer in my view controller, you can ofcourse name it anything.
Please let me know if you need further help.

how to implement integration router in java

i am trying to convert below XML snippet into Java version, appreciate any help here
<int:router input-channel="channel_in" default-output-channel="channel_default"
expression="payload.name" ignore-channel-name-resolution-failures="true">
<int:mapping value="foo" channel="channel_one" />
<int:mapping value="bar" channel="channel_two" />
</int:router>
Here is what i did for my concrete example
#Router(inputChannel = "routerChannel")
public String route(Account message) {
if (message.getType().equals("check")) {
return "checkChannel";
} else if (message.getType().equals("credit")) {
return "creditChannel";
}
return "errorChannel";
}
#Bean
public DirectChannel checkChannel() {
return new DirectChannel();
}
when i do above i am seeing below error
org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel 'application:8090.checkChannel'.;
All the Spring Integration custom tag has a description like this:
<xsd:element name="router" type="routerType">
<xsd:annotation>
<xsd:documentation>
Defines a Consumer Endpoint for the
'org.springframework.integration.router.AbstractMessageProcessingRouter' implementation
that serves as an adapter for invoking a method on any
Spring-managed object as specified by the "ref" and "method" attributes.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
So, it becomes pretty clear that we need some AbstractMessageProcessingRouter implementation to be present in the Java Configuration.
Also we have a paragraph in the Reference Manual like this:
With XML configuration and Spring Integration Namespace support, the XML Parsers hide how target beans are declared and wired together. For Java & Annotation Configuration, it is important to understand the Framework API for target end-user applications.
According your expression="payload.name" we need to look for the ExpressionEvaluatingRouter and then also read a chapter about #Bean configuration:
#Bean
#Router(inputChannel = "channel_in")
public ExpressionEvaluatingRouter expressionRouter() {
ExpressionEvaluatingRouter router = new ExpressionEvaluatingRouter("payload.name");
router.setDefaultOutputChannelName("channel_default");
router.setChannelMapping("foo", "channel_one");
router.setChannelMapping("bar", "channel_two");
return router;
}

Spring Integration - XML Schema validator - Dynamic schema file selection

I am trying to implement XML validation using Spring Integration <int-xml:validating-filter />. I followed the discussion in usage of spring integration's schema validator?. The problem statement is the same but with an additional parameter. Instead to hard coding the value in schema-location="xyz.xsd", rather I want to dynamically select the appropriate xsd file for respective incoming xml or DOMSource inputs.
I also followed http://forum.spring.io/forum/spring-projects/integration/121115-dynamic-schema-location-for-xml-validating-filter-component where Gary Russell mentioned:
There's no direct support for dynamic schemas, but you can provide a
custom XmlValidator using the xml-validator attribute (mutually
exclusive with schema location)
Once you've introspected your document to find the schema you wish to
validate against, simply delegate to a validator that has been
configured to validate against that schema.
You can use a XmlValidatorFactory to create each validator; see the
XmlValidatingMessageSelector for how to create a validator, once you
know the schema location
Since the comments dates back to the year 2012, is there an approach now in spring integration to validate input xml by dynamically selecting appropriate schema? If not can anyone provide an example on how to implement?
Following is my spring integration configuration:
<int:gateway id="applicationServiceGateway" service-interface="abc.IGateway"
default-request-channel="applicationRequestChannel" default-reply-channel="applicationResponseChannel"
error-channel="errorProcessingChannel" />
<int:chain id="serviceRequestValidation" input-channel="applicationRequestChannel" output-channel="responseChannel">
<!-- How to do -->
<int-xml:validating-filter xml-validator="xmlValidator"
schema-type="xml-schema"
throw-exception-on-rejection="true" />
<int:service-activator id="schematronValidationActivator" ref="schematronValidator" method="validate" />
</int:chain>
<bean id="xmlValidator" class="abc.validator.DomSourceValidator" />
Here is my Validator class defined:
import org.springframework.xml.validation.ValidationErrorHandler;
import org.springframework.xml.validation.XmlValidator;
import org.xml.sax.SAXParseException;
public class DomSourceValidator implements XmlValidator {
#Override
public SAXParseException[] validate(Source source) throws IOException {
/* How to implement this method?
Using XPath I can identify the root node from 'source' and then load
the appropriate XSD file. But don't know how to proceed
or what should be 'return'(ed) from here.
Any example is much appreciated.
*/
return null;
}
#Override
public SAXParseException[] validate(Source source, ValidationErrorHandler errorHandler) throws IOException {
// TODO Auto-generated method stub
return null;
}
}
Which is the best way of implementing the XML validator using Spring Integration?
There have been no changes since that comment.
As I said there, your validator needs to use XmlValidatorFactory to create a validator for each schema; then call a specific validator for each message; something like:
String schema = determineSchema(source);
XmlValidator val = lookupValidatorForSchema(schema);
if (val == null) {
// create a new one and add it to the map.
}
return val.validate(source);
If it helps other folks who are trying to do the same
Based on Gary's suggestion, I have come out with an implementation of XmlValidator by dynamically identifying input XML and then selecting appropriate Schema file to apply the validation.
Below is my spring integration configuration:
<int:gateway id="applicationServiceGateway" service-interface="abc.IGateway" default-request-channel="applicationRequestChannel" default-reply-channel="applicationResponseChannel" error-channel="errorProcessingChannel" />
<int:chain id="serviceRequestValidation" input-channel="applicationRequestChannel" output-channel="responseChannel">
<int-xml:validating-filter xml-validator="xmlValidator"
schema-type="xml-schema"
throw-exception-on-rejection="true" /> <!-- a MessageRejectedException is thrown in case validation fails -->
<int:service-activator id="schematronValidationActivator" ref="schematronValidator" method="validate" />
</int:chain>
<bean id="xmlValidator" class="abc.validator.DomSourceValidator">
<constructor-arg>
<map key-type="java.lang.String" value-type="java.lang.String">
<entry key="OTA_AirAvailRQ" value="common/schemas/FS_OTA_AirAvailRQ.xsd" />
<entry key="OTA_AirBookModifyRQ" value="common/schemas/FS_OTA_AirBookModifyRQ.xsd" />
<entry key="OTA_AirBookRQ" value="common/schemas/FS_OTA_AirBookRQ.xsd" />
</map>
</constructor-arg>
</bean>
To demonstrate I have used the OTA schema files to construct a map as constructor-arg. The map key is the root node from the XML file from the gateway and value is the location of the xsd file; and form the key-value pair map.
Refer to the below implementation class how this map is being used to identify the input XML and apply the validation.
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Assert;
import org.springframework.xml.validation.ValidationErrorHandler;
import org.springframework.xml.validation.XmlValidator;
import org.springframework.xml.validation.XmlValidatorFactory;
import org.xml.sax.SAXParseException;
public class DomSourceValidator implements XmlValidator {
private static final Log LOGGER = LogFactory.getLog(DomSourceValidator.class);
private Map<String, String> schemaMap;
private static Map<String, XmlValidator> validatorMap = new HashMap<>();
public DomSourceValidator(Map<String, String> schemaMap) {
this.schemaMap = schemaMap;
}
#PostConstruct
private void init() throws IOException {
LOGGER.info("Constructing Validators from schema resource list ...");
Assert.notEmpty(schemaMap, "No schema resource map found");
if (validatorMap.isEmpty()) {
XmlValidator validator = null;
for (Map.Entry<String, String> entry : schemaMap.entrySet()) {
validator = createValidatorFromResourceUri(entry.getValue());
validatorMap.put(entry.getKey(), validator);
}
}
}
#Override
public SAXParseException[] validate(Source source) throws IOException {
Assert.notNull(schemaMap, "No validator(s) defined");
XmlValidator validator = lookupValidator(source);
return validator.validate(source);
}
#Override
public SAXParseException[] validate(Source source, ValidationErrorHandler errorHandler) throws IOException {
// Skip implementation
return null;
}
private XmlValidator lookupValidator(Source source) {
String reqType = determineRequestType(source);
LOGGER.info("Loading validator for type: " + reqType);
XmlValidator xmlValidator = validatorMap.get(reqType);
Assert.notNull(xmlValidator, "No validator found for type: " + reqType);
return xmlValidator;
}
private String determineRequestType(Source source) {
if (source instanceof DOMSource) {
return ((DOMSource) source).getNode().getFirstChild().getNodeName();
}
return null;
}
private XmlValidator createValidatorFromResourceUri(String schemaResource) throws IOException {
Assert.notNull(schemaResource);
return XmlValidatorFactory.createValidator(new ClassPathResource(schemaResource), XmlValidatorFactory.SCHEMA_W3C_XML);
}
}
As soon as the spring bean id="xmlValidator" is initialized, the #PostConstruct kicks in to create Validator instances using XmlValidatorFactory from the resource URIs to have a pre-initialized validators.
If there is a validation error, a org.springframework.integration.MessageRejectedException: Message was rejected due to XML Validation errors is thrown (ref. throw-exception-on-rejection="true" in the <int-xml:validating-filter />).
The above implementation works perfectly fine for me. One can customize it further, or post another version to achieve the same.
Note
Instead of using a <int-xml:validating-filter />, one can also use a <int:service-activator /> in the <int-chain />, as logically <int-xml:validating-filter /> does not actually do any filter logic. But it serves the purpose.

Using optional facelet tag libraries

I have a web application that uses optional modules. The modules are implemented as Web Fragment projects, their jars may or may not be deployed with the war depending on the build profile.
A module can contain it's own module.taglib.xml with a http://company.com/module namespace and some tags.
The war xhtml templates use module tags like this:
<ui:composition ... xmlns:mod="http://company.com/module">
<c:if test="#{moduleDeployed}">
<mod:someTag />
</c:if>
Problems.
When the module is not deployed, the war pages work fine, but in ProjectStage.Development I get FacesMessage warnings:
Warning: This page calls for XML namespace
http://company.com/module declared with prefix mod but no
taglibrary exists for that namespace.
As far as I can see, JSF specification doesn't define what happens, when a template uses a nonexistent tag library. So with my current approach war pages can stop working after an upgrade or a switch to a different JSF implementation.
Questions.
Is there a (not very ugly) way to disable this specific warning?
Is there a better approach to using optional facelet tag libraries?
As of now I plan to disable the warning anyway I can: e.g. override Messages renderer and check message string if I have to. If the problem 2 manifests, make the build supply placeholder taglib.xml files for not deployed modules.
Even though placeholder taglibs seemed like a pretty good solution, they also seemed harder to implement and maintain.
So in the end I went with filtering the messages. This is likely Mojarra specific: the message text, the fact that the iterator allows removal (this isn't forbidden by the spec, but it's not required either). It's known to work with Mojarra 2.2.8 to 2.2.13.
public class SuppressNoTaglibraryExistsFacesMessage implements SystemEventListener {
private static final Pattern PTTRN_NO_TAGLIBRARY_EXISTS_FOR_NAMESPACE =
Pattern.compile("Warning: This page calls for XML namespace \\S+ declared with "
+ "prefix \\S+ but no taglibrary exists for that namespace.");
#Override
public void processEvent(SystemEvent event) {
Iterator<FacesMessage> messages = FacesContext.getCurrentInstance().getMessages();
while (messages.hasNext()) {
String messageSummary = messages.next().getSummary();
if (PTTRN_NO_TAGLIBRARY_EXISTS_FOR_NAMESPACE.matcher(messageSummary).matches()) {
messages.remove();
}
}
}
#Override
public boolean isListenerForSource(Object source) {
return true;
}
}
Bind the listener only in Development project stage.
public class SubscribeListenersAfterApplicationPostConstructListener
implements SystemEventListener {
#Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
Application application = (Application) event.getSource();
if (ProjectStage.Development.equals(application.getProjectStage())) {
application.subscribeToEvent(PostAddToViewEvent.class, UIViewRoot.class,
new SuppressNoTaglibraryExistsFacesMessage());
}
}
#Override
public boolean isListenerForSource(Object source) {
return source instanceof Application;
}
}
And in faces-config.xml:
<system-event-listener>
<system-event-listener-class><packages>.SubscribeListenersAfterApplicationPostConstructListener</system-event-listener-class>
<system-event-class>javax.faces.event.PostConstructApplicationEvent</system-event-class>
</system-event-listener>

Remove Log4net (null): Conditional Output

I'm trying to suppress "(null)" output. If I have this conversionPattern:
%property{MyProp}
...and if MyProp is null, then the output is:
(null)
In most cases that is good. But what if I don't want the "(null)" to be output? Can this be done?
The problem becomes more interesting if my conversionPattern looks like this:
MY_PROP=%property{MyProp}
In this case, even if I find a way to suppress the "(null)", my output will still be:
MY_PROP=
Ideally, I'd like to suppress everything related to "my prop" if the value is null. If I invented my own syntax, it might be:
%(MyProp,MY_PROP=%MyProp)
This would make log4net suppress the conversion pattern specified in the second parameter, if the first parameter was null.
My musings aside, is there a real way to do this in log4net?
One way to do this is to create a custom PatternLayoutConverter. If the property value is null write the customNullReplacement (that can even be based on the Option key if you want some dynamism).
namespace LogUtils
{
public class CustomNullProperty : PatternLayoutConverter
{
private static string customNullReplacement = "????";
override protected void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
if (Option != null)
{
var prop = loggingEvent.LookupProperty(Option);
if(prop == null)
{
prop = customNullReplacement;
}
WriteObject(writer, loggingEvent.Repository, prop);
}
else
{
WriteDictionary(writer, loggingEvent.Repository, loggingEvent.GetProperties());
}
}
}
}
You then define this converter – using the <converter> tag – on the Log4Net Configuration (XML based config example bellow) and use it on your conversionPattern:
<layout type="log4net.Layout.PatternLayout">
<converter>
<name value="customNullProp" />
<type value="LogUtils.CustomNullProperty, DLLNAME" />
</converter>
<conversionPattern value="customNullProp{PropertyName} %message%newline"/>
</layout>
There are other approaches like creating a renderer for System.Object but I find this approach more explicit.
For your second requirement, using the custom converter, you could simply move the prefix inside the custom converter writer.

Resources