Trying to create a generic method to avoid repetition of code - c#

I am working on a C# application, and I have 2 (soon to be 3, 4 and more) methods which have such a similar structure they are begging to be converted to something more generic. Here are 2 samples, you will see the similarities.
Method 1:
public async Task<APIGatewayProxyResponse> McaEventStoreRecvdPointsCouponProxyResponse(APIGatewayProxyRequest request, ILambdaContext context)
{
try
{
string thisRequestId = Guid.NewGuid().ToString();
if (request.PathParameters.Any())
{
var cardNumber = request.PathParameters.FirstOrDefault(x => x.Key.ToLower() == "card_number").Value;
context.Logger.LogLine($"MCA Event store event [{cardNumber}]");
var restValueVoucher = JsonConvert.DeserializeObject<RootObjectRestValueVoucherPayload>(request.Body);
RestValueVoucherPayloadValidator validator = new RestValueVoucherPayloadValidator();
ValidationResult results = validator.Validate(restValueVoucher.Payload);
if (!results.IsValid) throw new SchemaValidationException(results.Errors);
var dbRestValueVoucher = restValueVoucher.Payload.Convert(restValueVoucher.Payload);
dbRestValueVoucher.CardNumber = cardNumber;
loyaltyContext.Add(dbRestValueVoucher);
int rowsAffected = await loyaltyContext.SaveChangesAsync();
context.Logger.LogLine($"Database changes applied {rowsAffected}");
return GenerateResponse(HttpStatusCode.OK, new EventStoreResponse(context,
RequestResponseTypes.EVENT_STORE, thisRequestId,
restValueVoucher.Payload));
}
else
{
return GenerateResponse(HttpStatusCode.OK, new TestResponse(context, RequestResponseTypes.TEST_REQUEST));
}
}
catch (SchemaValidationException schemaEx)
{
context.Logger.LogLine(schemaEx.Message);
return GenerateResponse(HttpStatusCode.BadRequest, schemaEx);
}
catch (Exception ex)
{
context.Logger.LogLine($"{ex}");
LcsException lcsException = new LcsException(ex);
return GenerateResponse(HttpStatusCode.BadRequest,
lcsException);
}
}
Method 2:
public async Task<APIGatewayProxyResponse> McaEventStoreTierChangeProxyResponse(APIGatewayProxyRequest request, ILambdaContext context)
{
try
{
string thisRequestId = Guid.NewGuid().ToString();
if (request.PathParameters.Any())
{
var cardNumber = request.PathParameters.FirstOrDefault(x => x.Key.ToLower() == "card_number").Value;
context.Logger.LogLine($"MCA Event store event [{cardNumber}]");
var tierChange = JsonConvert.DeserializeObject<RootObjectTierChangePayload>(request.Body);
TierChangePayloadValidator validator = new TierChangePayloadValidator();
ValidationResult results = validator.Validate(tierChange.Payload);
if (!results.IsValid) throw new SchemaValidationException(results.Errors);
var dbTierChange = tierChange.Payload.Convert(tierChange.Payload);
dbTierChange.CardNumber = cardNumber;
loyaltyContext.Add(dbTierChange);
int rowsAffected = await loyaltyContext.SaveChangesAsync();
context.Logger.LogLine($"Database changes applied {rowsAffected}");
return GenerateResponse(HttpStatusCode.OK, new EventStoreResponse(context,
RequestResponseTypes.EVENT_STORE, thisRequestId,
tierChange.Payload));
}
else
{
return GenerateResponse(HttpStatusCode.OK, new TestResponse(context, RequestResponseTypes.TEST_REQUEST));
}
}
catch (SchemaValidationException schemaEx)
{
context.Logger.LogLine(schemaEx.Message);
return GenerateResponse(HttpStatusCode.BadRequest, schemaEx);
}
catch (Exception ex)
{
context.Logger.LogLine($"{ex}");
LcsException lcsException = new LcsException(ex);
return GenerateResponse(HttpStatusCode.BadRequest,
lcsException);
}
}
I started to work on the generic method, and got this far:
private static TPayload ProcessTest<TPayload, TEvent>(TPayload payload, TEvent myevent, string body, AbstractValidator<TPayload> validator)
where TPayload : Payload
where TEvent : IEventStore
{
var test = JsonConvert.DeserializeObject<TPayload>(body);
ValidationResult results = validator.Validate(?)
}
My issue is with refactoring this line at the moment: ValidationResult results = validator.Validate(tierChange.Payload). tierChange is a JSON 'Root object' that allows me to accept incoming JSON in the following format:
{
"Message": {
"message-id": 1000,
"old-tier": "SISTERCLUB",
"new-tier": "DIAMOND",
"timestamp-of-change": "2020-07-27T00:00:00",
"anniversary-date": "2020-07-28T00:00:00"
}
}
The structure is very similar to the incoming JSON for Method 1, which is:
{
"Message": {
"message-id": 10000,
"redeemed-voucher-instance-id":123,
"new-voucher-instance-id":1234,
"initial-voucher-value": 5.00,
"rest-voucher-value":15.00,
"valid-from": "2020-07-27T00:00:00",
"valid-to": "2021-07-27T00:00:00",
"description": "$5 BIRTHDAY VOUCHER",
"unit": "AUD"
}
}
The .Payload is used to access the content inside the root object in both cases, (content which is unique to each object). Here is an example of the Tier Change Root object and Payload (apart from different properties within Payload, the other object is the same).
The root object:
public class RootObjectTierChangePayload
{
[JsonProperty(PropertyName = "Message")]
public TierChangePayload Payload { get; set; }
}
And the inner object:
public partial class TierChangePayload : Payload, ITransform<TierChangePayload, TierChange>, IEventStore
{
[JsonProperty(PropertyName = "message-id")]
public int MessageId { get; set; }
/// <summary>
/// </summary>
[JsonProperty(PropertyName = "old-tier")]
public string OldTier { get; set; }
/// <summary>
/// </summary>
[JsonProperty(PropertyName = "new-tier")]
public string NewTier { get; set; }
/// <summary>
/// </summary>
[JsonProperty(PropertyName = "timestamp-of-change")]
public DateTime TimestampOfChange { get; set; }
/// <summary>
/// </summary>
[JsonProperty(PropertyName = "anniversary-date")]
public DateTime AnniversaryDate { get; set; }
public TierChange Convert(TierChangePayload source)
{
TierChange tierChange = new TierChange
{
CreatedTimestamp = Functions.GenerateDateTimeByLocale(),
ChangeTimestamp = null,
AnniversaryDate = this.AnniversaryDate,
MessageId = this.MessageId,
NewTierId = this.NewTier,
OldTierId = this.OldTier
};
return tierChange;
}
public string ToJson()
{
throw new NotImplementedException();
}
}
How can I adjust the objects I'm using so that I can better generalise them to suit the generic method? At the moment, I can't access .Payload in the generic method.

Update
In C# you can pass code blocks (delegates) to other code blocks as Action<T> type or Func<T> type (with a variable number of generic arguments).
Those types just encapsulate your code and are useful in the cases such as yours - where the method is almost the same save for a couple of lines. You can take those couple of lines and pass them as a parameter to the method.
Action<> is a code block that takes T arguments and returns void.
Func<> is a code block that takes 0 or several T1 arguments and returns a T result.
Note that when compiled, these code blocks turn into static methods, and are thus purely a syntactic sugar.
End update
So your generic method can look like this:
public async Task<APIGatewayProxyResponse> GenericMethod<T>(APIGatewayProxyRequest request, ILambdaContext context, Func<string, (T, ValidationResult, string)> validationFunc) where T: class
{
try
{
string thisRequestId = Guid.NewGuid().ToString();
if (request.PathParameters.Any())
{
var cardNumber = request.PathParameters.FirstOrDefault(x => x.Key.ToLower() == "card_number").Value;
context.Logger.LogLine($"MCA Event store event [{cardNumber}]");
var validationAndData = validationFunc(request.Body);
ValidationResult results = validationAndData.Item2;
if (!results.IsValid) throw new SchemaValidationException(results.Errors);
loyaltyContext.Add(validationAndData.Item1);
int rowsAffected = await loyaltyContext.SaveChangesAsync();
context.Logger.LogLine($"Database changes applied {rowsAffected}");
return GenerateResponse(HttpStatusCode.OK, new EventStoreResponse(context,
RequestResponseTypes.EVENT_STORE, thisRequestId,
validationAndData.Item3));
}
else
{
return GenerateResponse(HttpStatusCode.OK,
new TestResponse(context, RequestResponseTypes.TEST_REQUEST));
}
}
catch (SchemaValidationException schemaEx)
{
context.Logger.LogLine(schemaEx.Message);
return GenerateResponse(HttpStatusCode.BadRequest, schemaEx);
}
catch (Exception ex)
{
context.Logger.LogLine($"{ex}");
LcsException lcsException = new LcsException(ex);
return GenerateResponse(HttpStatusCode.BadRequest,
lcsException);
}
}
Then you can convert the other two like so:
public async Task<APIGatewayProxyResponse> McaEventStoreRecvdPointsCouponProxyResponse(APIGatewayProxyRequest request, ILambdaContext context)
{
return await GenericMethod(request, context, (body) => {
var restValueVoucher = JsonConvert.DeserializeObject<RootObjectRestValueVoucherPayload>(request.Body);
RestValueVoucherPayloadValidator validator = new RestValueVoucherPayloadValidator();
var dbRestValueVoucher = restValueVoucher.Payload.Convert(restValueVoucher.Payload);
dbRestValueVoucher.CardNumber = cardNumber;
return (dbRestValueVoucher, validator.Validate(restValueVoucher.Payload), restValueVoucher.Payload);
});
}
public async Task<APIGatewayProxyResponse> McaEventStoreTierChangeProxyResponse(APIGatewayProxyRequest request, ILambdaContext context)
{
return await GenericMethod(request, context, (body) => {
var tierChange = JsonConvert.DeserializeObject<RootObjectTierChangePayload>(request.Body);
TierChangePayloadValidator validator = new TierChangePayloadValidator();
var dbTierChange = tierChange.Payload.Convert(tierChange.Payload);
dbTierChange.CardNumber = cardNumber;
return (dbTierChange, validator.Validate(tierChange.Payload), tierChange.Payload);
});
}

If you make generic root object
public class RootObject<T>
{
[JsonProperty(PropertyName = "Message")]
public T Payload { get; set; }
}
It could work if the json can handle that.
var tierChange = JsonConvert.DeserializeObject<RootObject<TPayload>>(request.Body);
ValidationResult results = validator.Validate(tierChange.Payload);

Related

How to hide a specific schema in the Swagger UI?

I've implemented the Swagger Documentation in a web API made with C# (.NET Core 5).
I have 3 classes: DefaultCommandResult, SuccessCommandResult and ErrorCommandResult.
The DefaultCommandResult is the base class witch the other two inherit from. The object returned in the endpoint is either a SuccessCommandResult or ErrorCommandResult, and DefaultCommandResult is never returned.
What I need to do is hide the schema generated in the endpoints' responses related to the base class DefaultCommandResult:
The request in the controller:
public ActionResult<DefaultCommandResult<ServerIdCommand>> Get([FromQuery] RecoverServerIdCommand command)
{
try
{
var id = handler.RecoverServerId(command);
if (id is null)
return NotFound();
var output = new ServerIdCommand { Id = id };
var result = new SuccessCommandResult<ServerIdCommand>(output);
return Ok(result);
}
catch (ObjectNotFoundException ex)
{
var result = new ErrorCommandResult<string>(ex.Message);
return NotFound(result);
}
catch (Exception ex)
{
var result = new ErrorCommandResult<string>(ex.Message);
return BadRequest(result);
}
}
The DefaultCommandResult class:
[SwaggerSubType(typeof(SuccessCommandResult<object>))]
[SwaggerSubType(typeof(ErrorCommandResult<object>))]
public class DefaultCommandResult<T> where T : class
{
public bool Success { get; set; }
protected DefaultCommandResult(bool success)
{
Success = success;
}
}
The SuccessCommandResult class (ErrorCommandResult follows the same structure, but for errors):
[SwaggerDiscriminator("successCommandResult")]
public class SuccessCommandResult<T> : DefaultCommandResult<T> where T : class
{
public List<T> Data { get; private set; }
public SuccessCommandResult() : base(true) { }
public SuccessCommandResult(List<T> data) : base(true)
{
Data = data;
}
public SuccessCommandResult(T #object) : base(true)
{
if (Data is null)
Data = new List<T> { #object };
else
Data.Add(#object);
}
}
How can I achieve that using the Swashbuckle framework for C#? Thanks in advance.
The way I found a resolution for that was creating a Document Filter for the name of the schema:
public class SchemaNameFilter : IDocumentFilter
{
// w/o the filter: ServerIdCommandSuccessCommandResult. / with the filter: SuccessComandResult
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
foreach (var schema in swaggerDoc.Components.Schemas)
{
foreach (var schemaRepository in context.SchemaRepository.Schemas)
{
if (schema.Key.Equals(schemaRepository.Key))
{
switch (schema.Key)
{
case string x when !x.Equals(nameof(DefaultCommandResult)) && x.Contains(nameof(DefaultCommandResult)):
schema.Value.Title = nameof(DefaultCommandResult);
break;
case string x when !x.Equals(nameof(SuccessCommandResult<string>)) && x.Contains(nameof(SuccessCommandResult<string>)):
schema.Value.Title = nameof(SuccessCommandResult<string>);
break;
case string a when !a.Equals(nameof(OutputCommandResult)) && a.Contains(nameof(OutputCommandResult)):
schema.Value.Title = nameof(OutputCommandResult);
break;
}
}
}
}
}
}
What it does, basically, is rename the Swagger schema (with the incorrect name) to the name of the class.
Then, in the Startup class, add the document filter:
services.AddSwaggerGen(c =>
{
c.DocumentFilter<SchemaNameFilter>();
});

Better way to serialize with a timeout in c#.Net

My use case:
In a single threaded application, I need to serialize arbitrary classes for logging purposes.
The arbitrary classes are predominantly translated in an automated way from a massive VB6 application into .NET.
If serialized without a timeout, the serialization method will loop until it runs out of memory.
This is what I have currently:
internal class Serializer
{
private readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public volatile string result = null;
public volatile Func<string> toExecute = null;
public Thread thread;
public ManualResetEventSlim messageToSender = new ManualResetEventSlim(false);
public ManualResetEventSlim messageToReceiver = new ManualResetEventSlim(false);
public Serializer()
{
thread = new Thread(new ThreadStart(run));
thread.Start();
}
~Serializer()
{
try
{
if (messageToSender != null) messageToSender.Dispose();
}
catch { };
try
{
if (messageToReceiver != null) messageToReceiver.Dispose();
}
catch { };
}
public volatile bool ending = false;
public void run()
{
while (!ending)
{
try
{
if (toExecute != null)
{
result = toExecute();
}
messageToReceiver.Reset();
messageToSender.Set();
messageToReceiver.Wait();
}
catch (ThreadInterruptedException)
{
log.Warn("Serialization interrupted");
break;
}
catch (ThreadAbortException)
{
Thread.ResetAbort();
result = null;
}
catch (Exception ex)
{
log.Error("Error in Serialization", ex);
Console.WriteLine(ex);
break;
}
}
}
}
public class LocalStructuredLogging
{
private static volatile Serializer _serializer;
private static Serializer serializer
{
get
{
if (_serializer == null)
{
_serializer = new Serializer();
}
return _serializer;
}
}
public void LogStucturedEnd()
{
try
{
if (serializer != null)
{
serializer.ending = true;
serializer.thread.Interrupt();
}
}
catch { }
}
internal ConcurrentDictionary<long, bool> disallowedToSerialize = new ConcurrentDictionary<long, bool>();
public string TrySerialize<T>(T payload, [CallerLineNumber] int line = 0)
{
long hashEl = typeof(T).Name.GetHashCode() * line;
bool dummy;
unchecked
{
if (disallowedToSerialize.TryGetValue(hashEl, out dummy))
{
return "°,°";
}
}
serializer.toExecute = () =>
{
try
{
return Newtonsoft.Json.JsonConvert.SerializeObject(payload, new Newtonsoft.Json.JsonSerializerSettings() { ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore });
}
catch (Exception)
{
disallowedToSerialize.TryAdd(hashEl, false);
return "°°°";
}
};
try
{
serializer.messageToSender.Reset();
serializer.messageToReceiver.Set();
if (serializer.messageToSender.Wait(6000))
{
return Interlocked.Exchange(ref serializer.result, null);
}
serializer.toExecute = null;
serializer.thread.Abort();
serializer.messageToSender.Wait(2000);
disallowedToSerialize.TryAdd(hashEl, false);
return "°§°";
}
catch (Exception)
{
disallowedToSerialize.TryAdd(hashEl, false);
return "°-°";
}
}
}
The code is called as in the following (test is an arbitrary class instance):
var logger = new LocalStructuredLogging();
var rr5 = logger.TrySerialize(test);
Although it seems to do the job, there are some issues with it:
it has a dependency on Thread.Abort
it is time dependent, so it will thus produce varied results on a loaded system
every class instance is treated like every other class instance - no tweaking
...
So, are there any better solutions available ?
Based upon dbc's excellent answer, I managed to create a better timed serializer.
It resolves all 3 issues mentioned above:
public class TimedJsonTextWriter : JsonTextWriter
{
public int? MaxDepth { get; set; }
public TimeSpan? MaxTimeUsed { get; set; }
public int MaxObservedDepth { get; private set; }
private DateTime start = DateTime.Now;
public TimedJsonTextWriter(TextWriter writer, JsonSerializerSettings settings, TimeSpan? maxTimeUsed)
: base(writer)
{
this.MaxDepth = (settings == null ? null : settings.MaxDepth);
this.MaxObservedDepth = 0;
this.MaxTimeUsed = maxTimeUsed;
}
public TimedJsonTextWriter(TextWriter writer, TimeSpan? maxTimeUsed, int? maxDepth = null)
: base(writer)
{
this.MaxDepth = maxDepth;
this.MaxTimeUsed = maxTimeUsed;
}
public override void WriteStartArray()
{
base.WriteStartArray();
CheckDepth();
}
public override void WriteStartConstructor(string name)
{
base.WriteStartConstructor(name);
CheckDepth();
}
public override void WriteStartObject()
{
base.WriteStartObject();
CheckDepth();
}
uint checkDepthCounter = 0;
private void CheckDepth()
{
MaxObservedDepth = Math.Max(MaxObservedDepth, Top);
if (Top > MaxDepth)
throw new JsonSerializationException($"Depth {Top} Exceeds MaxDepth {MaxDepth} at path \"{Path}\"");
unchecked
{
if ((++checkDepthCounter & 0x3ff) == 0 && DateTime.Now - start > MaxTimeUsed)
throw new JsonSerializationException($"Time Usage Exceeded at path \"{Path}\"");
}
}
}
public class LocalStructuredLogging
{
public void LogStucturedEnd()
{
}
internal HashSet<long> disallowedToSerialize = new HashSet<long>();
public string TrySerialize<T>(T payload, int maxDepth = 100, int secondsToTimeout = 2, [CallerLineNumber] int line = 0)
{
long hashEl = typeof(T).Name.GetHashCode() * line;
if (disallowedToSerialize.Contains(hashEl))
{
return "°,°";
}
try
{
var settings = new JsonSerializerSettings { MaxDepth = maxDepth, ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore };
using (var writer = new StringWriter())
{
using (var jsonWriter = new TimedJsonTextWriter(writer, settings, new TimeSpan(0, 0, secondsToTimeout)))
{
JsonSerializer.Create(settings).Serialize(jsonWriter, payload);
// Log the MaxObservedDepth here, if you want to.
}
return writer.ToString();
}
}
catch (Exception)
{
disallowedToSerialize.Add(hashEl);
return "°-°";
}
}
}
The only issue remaining are the Hash collisions, which are easy to solve (e.g. by using the source file name as well or use another type of Collection).
The correct way to run an action timed would be to do something like the following. I would recommend taking a second look at how serialization should work as well :).
/// <summary>
/// Run an action timed.
/// </summary>
/// <param name="action">Action to execute timed.</param>
/// <param name="secondsTimout">Seconds before Task should cancel.</param>
/// <returns></returns>
public static async Task RunTimeout(Action action, int secondsTimout) {
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(secondsTimout));
await Task.Run(action, tokenSource.Token);
}
You may also want to return a variable upon the completion of your timed task. That can be done like so...
public static async Task<T> RunTimeout<T>(Func<T> action, int secondsTimout) {
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(secondsTimout));
var result = await Task.Run(action, tokenSource.Token);
return result;
}

How to typecast the object based on the string class name passed in the constructor - C#

This is my Custom attribute
public class CaptureMetrics : ActionFilterAttribute
{
private readonly string _metricType;
public CaptureMetrics(MetricTypes metricType)
{
_metricType = metricType.ToString();
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
try
{
string strJson;
var reqPayload = context.HttpContext.Request.Body;
using (var sr = new StreamReader(reqPayload))
{
strJson = await sr.ReadToEndAsync();
}
//Type t = Type.GetType(_metricType);
//below I need to typecast the object to the equivalent DTO
var obj = JsonConvert.DeserializeObject(strJson); //line #
// ToDo
}
catch (Exception)
{
//ToDo -> Log Exception
throw;
}
finally
{
await next();
}
}
}
public enum MetricTypes
{
ApiMetric
}
//DTO
public class ApiMetric
{
//some properties
}
From the controller's action the above attribute is used as below
[CaptureMetrics(MetricTypes.ApiMetric)]
public async Task<IActionResult> Get([FromBody]ApiPayload payload)
{
}
On line #, I need to typecast the request payload to the equivalent DTO class.
For every entry in Enum (MetricType), I have a class with the same name (ex. ApiMetric)
How to typecast the object here?

C# design guideline - calling appropriate method based on string value

Looking for design guidelines for the following problem.
I'm receiving two string values - action and message and have to call appropriate method which processes string message (processM1MessageVer1, processM1MessageVer2, processM2MessageVer1...). The method I have to call depends on the given string action. There are 2 versions (but in future there might be more) of each processing method. The version of method I have to call is determined by global variable version. Every method returns object of different type (ResultObject1, ResultObject2...). The result has to be serialized, converted to base64 and returned back.
Is there more elegant way of writing this (eliminate duplicate code, make possible future changes easier, reduce code...):
string usingVersion = "ver1";
public string processRequest(string action, string message)
if (usingVersion == "ver1"){
processRequestVer1(action, message);
}
else{
processRequestVer2(action, message);
}
}
//version 1
public string processRequestVer1(string action, string message){
string result = "";
switch (action){
case "m1":
ResultObject1 ro = processM1MessageVer1(message);
result = serialize(ro);
result = convertToB64(result);
case "m2":
ResultObject2 ro = processM2MessageVer1(message);
result = serialize(ro);
result = convertToB64(result);
case "m3":
ResultObject3 ro = processM3MessageVer1(message);
result = serialize(ro);
result = convertToB64(result);
}
return result;
}
//version 2
public string processRequestVer2(string action, string message){
string result = "";
switch (action){
case "m1":
ResultObject1 ro = processM1MessageVer2(message);
result = serialize(ro);
result = convertToB64(result);
case "m2":
ResultObject2 ro = processM2MessageVer2(message);
result = serialize(ro);
result = convertToB64(result);
case "m3":
ResultObject3 ro = processM3MessageVer2(message);
result = serialize(ro);
result = convertToB64(result);
}
return result;
}
It would be simplier if messages that have to be processed are of different object types instead of strings so that appropriate method could be called polymorphically. The fact that every process method returns different object type also complicates things even more. But these don't depend on me and I cannot change it.
My approach (make it more object oriented, and you should justify whether it's appropriate to create class structure depending on how complex your processing logic is. If your processing logic is only little then maybe this is over-engineering):
For serialize and convert to base 64, I assume you have some logic to do those tasks in a generic way. If not, move those to sub class also
public interface IRequestProcessorFactory
{
IRequestProcessor GetProcessor(string action);
}
public class FactoryVersion1 : IRequestProcessorFactory
{
public IRequestProcessor GetProcessor(string action)
{
switch(action)
{
case "m1":
return new M1Ver1RequestProcessor();
case "m2":
return new M2Ver1RequestProcessor();
case "m3":
return new M3Ver1RequestProcessor();
default:
throw new NotSupportedException();
}
}
}
public class FactoryVersion2 : IRequestProcessorFactory
{
public IRequestProcessor GetProcessor(string action)
{
switch(action)
{
case "m1":
return new M1Ver2RequestProcessor();
case "m2":
return new M2Ver2RequestProcessor();
case "m3":
return new M3Ver2RequestProcessor();
default:
throw new NotSupportedException();
}
}
}
public interface IRequestProcessor
{
string ProcessRequest(string message);
}
public class RequestProcessorBase<T>
{
public string ProcessRequest(string message)
{
T result = Process(message);
string serializedResult = Serialize(result);
return ConvertToB64(serializedResult);
}
protected abstract T Process(string message);
private string Serialize(T result)
{
//Serialize
}
private string ConvertToB64(string serializedResult)
{
//Convert
}
}
public class M1Ver1RequestProcessor : RequestProcessorBase<ResultObject1>
{
protected ResultObject1 Process(string message)
{
//processing
}
}
public class M2Ver1RequestProcessor : RequestProcessorBase<ResultObject2>
{
protected ResultObject2 Process(string message)
{
//processing
}
}
public class M3Ver1RequestProcessor : RequestProcessorBase<ResultObject3>
{
protected ResultObject3 Process(string message)
{
//processing
}
}
public class M1Ver2RequestProcessor : RequestProcessorBase<ResultObject1>
{
protected ResultObject1 Process(string message)
{
//processing
}
}
public class M2Ver2RequestProcessor : RequestProcessorBase<ResultObject2>
{
protected ResultObject2 Process(string message)
{
//processing
}
}
public class M3Ver2RequestProcessor : RequestProcessorBase<ResultObject3>
{
protected ResultObject3 Process(string message)
{
//processing
}
}
Usage:
string action = "...";
string message = "...";
IRequestProcessorFactory factory = new FactoryVersion1();
IRequestProcessor processor = factory.GetProcessor(action);
string result = processor.ProcessRequest(message);
The switch is still there in factory class, but it only returns processor and doesn't do actual work so it's fine for me
First - define interface that suit you best, like this
public interface IProcessMessage
{
string ActionVersion { get; }
string AlgorithmVersion { get; }
string ProcessMessage(string message);
}
Then create as many implementation as you need
public class processorM1Ver1 : IProcessMessage
{
public string ProcessMessage(string message)
{
ResultObject1 ro1 = processM1MessageVer1(message);
var result = serialize(ro1);
result = convertToB64(result);
return result;
}
public string ActionVersion {get { return "m1"; }}
public string AlgorithmVersion {get { return "ver1"; }}
}
public class processorM2Ver1 : IProcessMessage
{
public string ActionVersion {get { return "m2"; }}
public string AlgorithmVersion {get { return "ver1"; }}
public string ProcessMessage(string message)
{
ResultObject1 ro1 = processM2MessageVer1(message);
var result = serialize(ro1);
result = convertToB64(result);
return result;
}
}
public class processorM1Ver2 : IProcessMessage
{
public string ActionVersion {get { return "m1"; }}
public string AlgorithmVersion {get { return "ver2"; }}
public string ProcessMessage(string message)
{
ResultObject1 ro1 = processM1MessageVer2(message);
var result = serialize(ro1);
result = convertToB64(result);
return result;
}
}
Now you need something that know which implementation is best in current context
public class MessageProcessorFactory
{
private MessageProcessorFactory() { }
private static readonly MessageProcessorFactory _instance = new MessageProcessorFactory();
public static MessageProcessorFactory Instance { get { return _instance; }}
private IEnumerable<IProcessMessage> _processorCollection;
IEnumerable<IProcessMessage> ProcessorCollection
{
get
{
if (_processorCollection == null)
{
//use reflection to find all imlementation of IProcessMessage
//or initialize it manualy
_processorCollection = new List<IProcessMessage>()
{
new processorM1Ver1(),
new processorM2Ver1(),
new processorM1Ver2()
};
}
return _processorCollection;
}
}
internal IProcessMessage GetProcessor(string action)
{
var algorithVersion = ReadAlgorithVersion();
var processor = ProcessorCollection.FirstOrDefault(x => x.AlgorithmVersion == algorithVersion && x.ActionVersion == action);
return processor;
}
private string ReadAlgorithVersion()
{
//read from config file
//or from database
//or where this info it is kept
return "ver1";
}
}
It can be use in such way
public class Client
{
public string ProcessRequest(string action, string message)
{
IProcessMessage processor = MessageProcessorFactory.Instance.GetProcessor(action);
return processor.ProcessMessage(message);
}
}

Cannot return IObservable<T> from a method marked async

This contrived example is roughly how my code is structured:
public abstract class SuperHeroBase
{
protected SuperHeroBase() { }
public async Task<CrimeFightingResult> FightCrimeAsync()
{
var result = new CrimeFightingResult();
result.State = CrimeFightingStates.Fighting;
try
{
await FightCrimeOverride(results);
}
catch
{
SetError(results);
}
if (result.State == CrimeFightingStates.Fighting)
result.State = CrimeFightingStates.GoodGuyWon;
return result;
}
protected SetError(CrimeFightingResult results)
{
result.State = CrimeFightingStates.BadGuyWon;
}
protected abstract Task FightCrimeOverride(CrimeFightingResult results);
}
public enum CrimeFightingStates
{
NotStarted,
Fighting,
GoodGuyWon, // success state
BadGuyWon // error state
}
public class CrimeFightingResult
{
internal class CrimeFightingResult() { }
public CrimeFightingStates State { get; internal set; }
}
Now I'm trying to build a collection that would hold multiple SuperHero objects and offer a AllHerosFightCrime method. The hero's should not all fight at once (the next one starts when the first is finished).
public class SuperHeroCollection : ObservableCollection<SuperHeroBase>
{
public SuperHeroCollection() { }
// I mark the method async...
public async IObservable<CrimeFightingResult> AllHerosFightCrime()
{
var heros = new List<SuperHeroBase>(this);
var results = new ReplaySubject<CrimeFightingResult>();
foreach (var hero in heros)
{
// ... so I can await on FightCrimeAsync and push
// the result to the subject when done
var result = await hero.FightCrimeAsync();
results.OnNext(result);
}
results.OnCompleted();
// I can't return the IObservable here because the method is marked Async.
// It expects a return type of CrimeFightingResult
return results;
}
}
How can I return the IObservable<CrimeFightingResults> and still have the call to FightCrimeAsync awaited?
You could turn your task into an observable and combine them using Merge:
public IObservable<CrimeFightingResult> AllHerosFightCrime()
{
var heros = new List<SuperHeroBase>(this);
return heros.Select(h => h.FightCrimeAsync().ToObservable())
.Merge();
}
If you want to maintain the order your events are received you can use Concat instead of Merge.

Categories

Resources