registering PDX type in type registry with Geode C# native client - gemfire

I am trying to implement a very simple PDX autoserialization in Geode. I've created a domain class of my own with a zero arg constructor:
public class TestPdx
{
public string Test1 { get; set; }
public string Test2 { get; set; }
public string Test3 { get; set; }
public TestPdx() { }
}
Now I want this class to auto serialize. I start a server cache with the following cache.xml where I attempt to register this type for auto PDX:
<?xml version="1.0" encoding="UTF-8"?>
<cache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://geode.apache.org/schema/cache"
xsi:schemaLocation="http://geode.apache.org/schema/cache
http://geode.apache.org/schema/cache/cache-1.0.xsd"
version="1.0">
<cache-server/>
<pdx>
<pdx-serializer>
<class-name>org.apache.geode.pdx.ReflectionBasedAutoSerializer</class-name>
<parameter name="classes"><string>TestPdx</string></parameter>
</pdx-serializer>
</pdx>
<region name="webclient" refid="REPLICATE_PERSISTENT"/>
</cache>
and then run the following code:
static void Main(string[] args)
{
// 1. cache
CacheFactory cacheFactory = CacheFactory.CreateCacheFactory();
Cache cache = cacheFactory
.SetSubscriptionEnabled(true)
.SetPdxReadSerialized(true)
.Create();
Serializable.RegisterPdxSerializer(new ReflectionBasedAutoSerializer());
RegionFactory regionFactory = cache.CreateRegionFactory(RegionShortcut.CACHING_PROXY);
IRegion<string, TestPdx> region = regionFactory.Create<string, TestPdx>("webclient");
// 3. TestPx object
TestPdx t = new TestPdx();
t.Test1 = "test1";
t.Test2 = "test2";
t.Test3 = "test3";
region["1"] = t;
// 4. Get the entries
TestPdx result1 = region["1"];
// 5. Print result
Console.WriteLine(result1.Test1);
Console.WriteLine(result1.Test2);
Console.WriteLine(result1.Test3);
}
This code is crashing at line region["1"] = t; with error
GFCLI_EXCEPTION:System.Runtime.InteropServices.SEHException (0x80004005): External component has thrown an exception.
at apache.geode.client.SerializationRegistry.GetPDXIdForType(SByte* , SharedPtr<apache::geode::client::Serializable>* )
So I haven't registered the PDX type properly. How do you do that with native client?
THANKS

An answer here is to implement IPdxSerializable in the TestPdx as follows:
public class TestPdx : IPdxSerializable
{
public string Test1 { get; set; }
public string Test2 { get; set; }
public string Test3 { get; set; }
public int Pid { get; set; }
public void ToData(IPdxWriter writer)
{
writer.WriteString("Test1", Test1);
writer.WriteString("Test2", Test2);
writer.WriteString("Test3", Test3);
writer.WriteInt("Pid", Pid);
writer.MarkIdentityField("Pid");
}
public void FromData(IPdxReader reader)
{
Test1 = reader.readString("Test1");
Test2 = reader.readString("Test2");
Test3 = reader.readString("Test3");
Pid = reader.readInt("Pid");
}
public static IPdxSerializable CreateDeserializable()
{
return new TestPdx();
}
public TestPdx() { }
}
and then register the Pdx type in the Geode, and use a region of type object or type TestPdx as follows:
Serializable.RegisterPdxType(TestPdx.CreateDeserializable);
IRegion<string, Object> t = regionFactory.Create<string, Object>("test");
and to write the TestPdx to the region simply:
TestPdx value = new TestPdx();
value.Test1 = "hello";
value.Test2 = "world";
value.Test3 = "again";
t[key] = value;
and there will be a PdxInstance in the Geode region so you can run OQL queries on it, etc.

Related

Setting up examples in Swagger

I am using Swashbuckle.AspNetCore.Swagger (1.0.0) and Swashbuckle.AspNetCore.SwaggerGen (1.0.0). I am trying to add default examples to my API following Default model example in Swashbuckle (Swagger). I created a new class file and added,
public class SwaggerDefaultValue : Attribute
{
public string ParameterName { get; set; }
public string Value { get; set; }
public SwaggerDefaultValue(string parameterName, string value)
{
this.ParameterName = parameterName;
this.Value = value;
}
}
public class AddDefaultValues : IOperationFilter
{
public void Apply(Operation operation, DataTypeRegistry dataTypeRegistry, ApiDescription apiDescription)
{
foreach (var param in operation.Parameters)
{
var actionParam = apiDescription.ActionDescriptor.GetParameters().First(p => p.ParameterName == param.Name);
if (actionParam != null)
{
var customAttribute = actionParam.ActionDescriptor.GetCustomAttributes<SwaggerDefaultValue>().FirstOrDefault();
if (customAttribute != null)
{
param.DefaultValue = customAttribute.Value;
}
}
}
}
}
but I get this error - AddDefaultValues does not implement interface member IOperationFilter.Apply(Operation, OperationFilterContext)
That link you are following is not for the Swashbuckle.AspNetCore version
Look in the correct project for the proper examples:
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/search?q=IOperationFilter&unscoped_q=IOperationFilter

Change injected object at runtime

I want to have multiples implementation of the IUserRepository each implementation will work with a database type either MongoDB or any SQL database. To do this I have ITenant interface that have a connection string and other tenant configuration. The tenant is been injected into IUserRepository either MongoDB or any SQLDB implementation. What I need to know is how properly change the injected repository to choose the database base on the tenant.
Interfaces
public interface IUserRepository
{
string Login(string username, string password);
string Logoff(Guid id);
}
public class User
{
public Guid Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
public interface ITenant
{
string CompanyName { get; }
string ConnectionString { get; }
string DataBaseName { get; }
string EncriptionKey { get; }
}
Is important to know that the tenant id is been pass to an API via header request
StartUp.cs
// set inject httpcontet to the tenant implemantion
services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
// inject tenant
services.AddTransient<ITenant, Tenant>();
// inject mongo repository but I want this to be programmatically
services.AddTransient<IUserRepository, UserMongoRepository>();
Sample Mongo Implementation
public class UserMongoRepository : IUserRepository
{
protected ITenant Tenant
public UserMongoRepository(ITenant tenant) :
base(tenant)
{
this.Tenant = tenant;
}
public string Login(string username, string password)
{
var query = new QueryBuilder<User>().Where(x => x.Username == username);
var client = new MongoClient(this.Tenant.ConnectionString);var server = client.GetServer();
var database = client.GetServer().GetDatabase(this.Tenant.DataBaseName);
var user = database.GetCollection<User>.FindAs<User>(query).AsQueryable().FirstOrDefault();
if (user == null)
throw new Exception("invalid username or password");
if (user.Password != password)
throw new Exception("invalid username or password");
return "Sample Token";
}
public string Logoff(Guid id)
{
throw new NotImplementedException();
}
}
Tenant
public class Tenant : ITenant
{
protected IHttpContextAccessor Accesor;
protected IConfiguration Configuration;
public Tenant(IHttpContextAccessor accesor, IDBConfiguration config)
{
this.Accesor = accesor;
this.Configuration = new Configuration().AddEnvironmentVariables();
if (!config.IsConfigure)
config.ConfigureDataBase();
}
private string _CompanyName;
public string CompanyName
{
get
{
if (string.IsNullOrWhiteSpace(_CompanyName))
{
_CompanyName = this.Accesor.Value.Request.Headers["Company"];
if (string.IsNullOrWhiteSpace(_CompanyName))
throw new Exception("Invalid Company");
}
return _CompanyName;
}
}
private string _ConnectionString;
public string ConnectionString
{
get
{
if (string.IsNullOrWhiteSpace(_ConnectionString))
{
_ConnectionString = this.Configuration.Get(this.CompanyName + "_" + "ConnectionString");
if (string.IsNullOrWhiteSpace(_ConnectionString))
throw new Exception("Invalid ConnectionString Setup");
}
return _ConnectionString;
}
}
private string _EncriptionKey;
public string EncriptionKey
{
get
{
if (string.IsNullOrWhiteSpace(_EncriptionKey))
{
_EncriptionKey = this.Configuration.Get(this.CompanyName + "_" + "EncriptionKey");
if (string.IsNullOrWhiteSpace(_EncriptionKey))
throw new Exception("Invalid Company Setup");
}
return _EncriptionKey;
}
}
private string _DataBaseName;
public string DataBaseName
{
get
{
if (string.IsNullOrWhiteSpace(_DataBaseName))
{
_DataBaseName = this.Configuration.Get(this.CompanyName + "_" + "DataBaseName");
if (string.IsNullOrWhiteSpace(_DataBaseName))
throw new Exception("Invalid Company Setup");
}
return _DataBaseName;
}
}
}
Controller
public class UsersController : Controller
{
protected IUserRepository DataService;
public UsersController(IUserRepository dataService)
{
this.DataService = dataService;
}
// the controller implematation
}
You should define a proxy implementation for IUserRepository and hide the actual implementations behind this proxy and at runtime decide which repository to forward the call to. For instance:
public class UserRepositoryDispatcher : IUserRepository
{
private readonly Func<bool> selector;
private readonly IUserRepository trueRepository;
private readonly IUserRepository falseRepository;
public UserRepositoryDispatcher(Func<bool> selector,
IUserRepository trueRepository, IUserRepository falseRepository) {
this.selector = selector;
this.trueRepository = trueRepository;
this.falseRepository = falseRepository;
}
public string Login(string username, string password) {
return this.CurrentRepository.Login(username, password);
}
public string Logoff(Guid id) {
return this.CurrentRepository.Logoff(id);
}
private IRepository CurrentRepository {
get { return selector() ? this.trueRepository : this.falseRepository;
}
}
Using this proxy class you can easily create a runtime predicate that decides which repository to use. For instance:
services.AddTransient<IUserRepository>(c =>
new UserRepositoryDispatcher(
() => c.GetRequiredService<ITenant>().DataBaseName.Contains("Mongo"),
trueRepository: c.GetRequiredService<UserMongoRepository>()
falseRepository: c.GetRequiredService<UserSqlRepository>()));
You can try injecting a factory rather than the actual repository. The factory will be responsible for building the correct repository based on the current user identity.
It might require a little more boiler plate code but it can achieve what you want. A little bit of inheritance might even make the controller code simpler.

NServiceBus lost messages

I have some messages that are disappearing only in Production.
In the logs it shows the method HandleBeginMessage() being called immediately followed by HandleEndMessage() without any handlers being called in the middle (this only happens sometimes!!!!)
public class OracleMessageModule : IMessageModule
{
public OracleMessageModule()
{
Factory = new OracleSagaSessionFactory();
}
public OracleSagaSessionFactory Factory { get; set; }
public void HandleBeginMessage()
{
Factory.Begin();
}
public void HandleEndMessage()
{
Factory.Complete();
}
public void HandleError()
{
Factory.Complete();
}
}
So I upgraded NServiceBus from 3.2.8 to 4.6.1 (latest version at the moment) and refactored the code:
public class OracleMessageModule : UnitOfWork.IManageUnitsOfWork
{
public OracleMessageModule()
{
Factory = new OracleSagaSessionFactory();
}
public OracleSagaSessionFactory Factory { get; set; }
public void Begin()
{
Factory.Begin();
}
public void End(System.Exception ex = null)
{
Factory.Complete();
}
}
There's the code for OracleSagaSessionFactory:
public class OracleSagaSessionFactory
{
public string ConnectionString { get; set; }
[ThreadStatic]
private static OracleSagaSession current;
public OracleSagaSession Begin()
{
if (current != null)
throw new InvalidOperationException("Current session already exists.");
var session = new OracleSagaSession(ConnectionString);
current = session;
return current;
}
public static OracleSagaSession Current
{
get
{
return current;
}
}
public void Complete()
{
var c = current;
current = null;
if (c != null)
c.Dispose();
}
}
Looking at NServiceBus code looks like in the new version it cleans the Pipeline, so that's why I believe this is the solution, and I don't believe this is my code problem...
Anyone agrees or disagrees?
Edit1: It only happens on my master node with multiple threads and just sometimes. It doesn't happen on my slave nodes that use just one thread.

Class serialization and namespace (xmlns)

I have a class that I am serializing
public partial class Security : MessageHeader
{
private Assertion assertionField;
[System.Xml.Serialization.XmlElementAttribute(Namespace = "urn:oasis:names:tc:SAML:2.0:assertion")]
public Assertion Assertion
{
get
{
return this.assertionField;
}
set
{
this.assertionField = value;
}
}
public override string Name
{
get { return "Security"; }
}
public override string Namespace
{
get { return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; }
}
[XmlIgnoreAttribute]
public string UserID { get; set; }
[XmlIgnoreAttribute]
public string FirstName { get; set; }
[XmlIgnoreAttribute]
public string LastName { get; set; }
[XmlIgnoreAttribute]
public string ReasonForSearch { get; set; }
public Security()
{
Assertion = new Assertion(UserID, FirstName, LastName, ReasonForSearch);
}
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("saml", "urn:oasis:names:tc:SAML:2.0:assertion");
XmlSerializer serializer = new XmlSerializer(typeof(Assertion));
serializer.Serialize(writer, Assertion, ns);
}
}
this is how i am adding code header
using (OperationContextScope scope = new OperationContextScope(healthixClient.InnerChannel))
{
Security msgHdr = new Security();
msgHdr.UserID = "TestUserID";
msgHdr.FirstName = "TestUserFirstName";
msgHdr.LastName = "TestUserLastName";
msgHdr.ReasonForSearch = "ReasonForSearch";
OperationContext.Current.OutgoingMessageHeaders.Add(msgHdr);
}
when i serialize this and add in my code header it looks like this
<Security xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<saml:Assertion ID="saml_6691a2b1-2a08-4d10-9d90-b006727d0e02" IssueInstant="2013-09-09T15:38:16Z" Version="2.0" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
< rest of the Xml is correct >
Now if I only change my override OnWriteHeaderContents method to
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("saml", "urn:oasis:names:tc:SAML:2.0:assertion");
XmlSerializer serializer = new XmlSerializer(typeof(Security));
serializer.Serialize(writer, new Security(), ns);
}
the header looks like this
<Security xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<Security xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
<saml:Assertion ID="saml_6691a2b1-2a08-4d10-9d90-b006727d0e02" IssueInstant="2013-09-09T15:29:09Z" Version="2.0">
< rest of the Xml is correct >
What i want the header to look like is this
<Security xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
<saml:Assertion ID="saml_6691a2b1-2a08-4d10-9d90-b006727d0e02" IssueInstant="2013-07-29T20:17:30.846Z" Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
Try this option in OnWriteHeaderContents method
writer.WriteStartElement("saml", "Assertion", "urn:oasis:names:tc:SAML:2.0:assertion");
writer.WriteString("Value");
writer.WriteEndElement();

MemberInfo not found for enum custom attribute

I can't seem to be able to read a custom enum attribute using the following code:
public class CustomAttribute : Attribute
{
public CultureAttribute (string value)
{
Value = value;
}
public string Value { get; private set; }
}
public enum EnumType
{
[Custom("value1")]
Value1,
[Custom("value2")]
Value2,
[Custom("value3")]
Value3
}
...
var memInfo = typeof(CustomAttribute).GetMember(EnumType.Value1.ToString());
// memInfo is always empty
var attributes = memInfo[0].GetCustomAttributes(typeof(CustomAttribute), false);
Not sure if I am just missing something obvious here or if there is an issue with reading custom attributes in Mono/MonoMac.
You should call the GetMember() on the type that has the given member of course :) In this case that's EnumType not CustomAttribute.
Also fixed what I assume to be copy-paste error with the attribute constructor.
Complete working test code (you should know with 23k reps that we prefer these to half-made programs that we have to complete ourselves ;)):
using System;
public class CustomAttribute : Attribute
{
public CustomAttribute (string value)
{
Value = value;
}
public string Value { get; private set; }
}
public enum EnumType
{
[Custom("value1")]
Value1,
[Custom("value2")]
Value2,
[Custom("value3")]
Value3
}
class MainClass
{
static void Main(string[] args)
{
var memInfo = typeof(EnumType).GetMember(EnumType.Value1.ToString());
Console.WriteLine("memInfo length is {0}", memInfo.Length);
var attributes = memInfo[0].GetCustomAttributes(typeof(CustomAttribute), false);
Console.WriteLine("attributes length is {0}", attributes.Length);
}
}
See in operation.