Mapper issue in AutoMapper 10.1.1 version - automapper

Seeing the below error from the AutoMapper flow after upgrading from 8.0 to 10.1.1 AutoMapper
Usage:
var response = new RAccountDetailResponse
{
retailerAccount = _mapper.Map<RAccountDetail>(retailer)
};
Error
System.ArgumentException: Expression of type 'PRM.Web.Core.Interfaces.Models.IRAccountDetailModel' cannot be used for parameter of type 'PRM.Web.Api.Contracts.RAccountDetail' (Parameter 'arg1')
at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index)
at System.Linq.Expressions.Expression.Invoke(Expression expression, Expression arg0, Expression arg1, Expression arg2)
at System.Linq.Expressions.Expression.Invoke(Expression expression, IEnumerable`1 arguments)
at System.Linq.Expressions.Expression.Invoke(Expression expression, Expression[] arguments)
at AutoMapper.MapperConfiguration.GenerateTypeMapExpression(MapRequest mapRequest, TypeMap typeMap)
at AutoMapper.MapperConfiguration.BuildExecutionPlan(MapRequest mapRequest)
at AutoMapper.MapperConfiguration.CompileExecutionPlan(MapRequest mapRequest)
at AutoMapper.Internal.LockingConcurrentDictionary`2.<>c__DisplayClass2_1.<.ctor>b__1()
at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
--- End of stack trace from previous location where exception was thrown ---
at System.Lazy`1.CreateValue()
at AutoMapper.Internal.LockingConcurrentDictionary`2.GetOrAdd(TKey key)
at AutoMapper.MapperConfiguration.GetExecutionPlan(MapRequest mapRequest)
at AutoMapper.MapperConfiguration.GetExecutionPlan[TSource,TDestination](MapRequest mapRequest)
at AutoMapper.Mapper.MapCore[TSource,TDestination](TSource source, TDestination destination, ResolutionContext context, Type sourceType, Type destinationType, IMemberMap memberMap)
at AutoMapper.Mapper.Map[TSource,TDestination](TSource source, TDestination destination)
at AutoMapper.Mapper.Map[TDestination](Object source)

Configure AutoMapper to know what types you want to map
var configuration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<retailer, RAccountDetail>();
});
Validate your mappings (remove it before release)
configuration.AssertConfigurationIsValid();
Create the mapper
var mapper = configuration.CreateMapper();
Execute the mappings:
var retailerAccount = mapper.Map<retailer, RAccountDetail>(retailModel);
Example
// Map BankModel(source) to Bank (destination)
public void CreateBank(BankModel bankModel)
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<BankModel, Bank>());
var mapper = config.CreateMapper();
var bank = mapper.Map<BankModel, Bank>(bankModel);
service.CreateBank(bank);
}
// Map Bank (source) to BankModel (destination)
public BankModel ReadBank(int ID)
{
var config = new MapperConfiguration(cfg => cfg.CreateMap<Bank, BankModel>());
var mapper = config.CreateMapper();
var bank = service.ReadBank(ID);
return mapper.Map<Bank, BankModel>(bank);
}

Related

Migrating a filter from XML to pure Java Config

I am trying to migrate a project from a mixed XML/Java configuration to pure Java Config (not yet Java DSL, but annotated #Bean methods).
So far, I managed to convert channels, inbound channel adapters, transformers and service activators), but I'm stuck with the conversion of a filter.
The integration.xml file define the following filter (the Message carries a Java.io.File payload)
<int:filter input-channel="channelA" output-channel="channelB"
ref="integrationConfiguration" method="selector"/>
The selector is defined in the IntegrationConfiguration class (that also holds all other SI-related #Bean methods:
#Configuration
public class IntegrationConfiguration {
// channels
#Bean
public MessageChannel channelA() { return new DirectChannel(); }
#Bean
public MessageChannel channelB() { return new DirectChannel(); }
// some other required channels
// ...
// inbound channel adapters
#Bean
#InboundChannelAdapter(channel = "channelA")
public MessageSource<File> fileReadingMessageSource() {
var source = new FileReadingMessageSource();
// source configuration (not relevant here)
return source;
}
// ...
// filter on Message<File>
public boolean selector(#Header("file_name") String name,
#Header("file_relativePath") String relativePath) {
// do stuff with name and relativePath and return true or false
return true;
}
// transformers
#Bean
#Transformer(inputChannel = "channelB", outputChannel = "channelC")
public HeaderEnricher enrichHeaders() {
var expression = new SpelExpressionParser().parseExpression("...");
var headers = Map.of("additional_header",
new ExpressionEvaluatingHeaderValueMessageProcessor<>(expression, String.class));
return new HeaderEnricher(headers);
}
// ...
// service activators
#Bean
#ServiceActivator(inputChannel = "channelC")
public FileWritingMessageHandler fileWritingMessageHandler() {
var handler = new FileWritingMessageHandler(
new SpelExpressionParser().parseExpression("headers.additional_header")
);
// handler configuration (not relevant here)
return handler;
}
// ...
}
I tried to replace the XML-defined bean with:
#Bean
#Filter(inputChannel = "channelA", outputChannel = "channelB")
public boolean filter() {
// get the "file_name" and "file_relativePath" headers
var expression1 = new SpelExpressionParser().parseExpression("headers.file_name");
var name = expression1.getValue(String.class);
var expression2 = new SpelExpressionParser().parseExpression("headers.file_relativePath");
String relativePath = expression2.getValue(String.class);
// do stuff with name and relativePath and return true or false
return true;
}
When I run the code, it gives me a BeanCreationException:
Error creating bean with name 'filter' defined in class path resource [.../IntegrationConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [boolean]: Factory method 'filter' threw exception; nested exception is org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'headers' cannot be found on null
What did I do wrong?
UPDATE after Artem's answer and insightful comments:
Using #Bean isn't necessary for a POJO method, keep it for
out-of-the-box type: MessageHandler, Transformer, MessageSelector etc
In this case, one can use a (not an out-of-the-box) #Bean MessageSelector, but it is actually more lines of code for the same result:
#Bean
#Filter(inputChannel = "channelA", outputChannel = "channelB")
public MessageSelector messageSelector() {
return new MessageSelector(){
#Override
public boolean accept(Message<?>message){
var headers = message.getHeaders();
var name = headers.get("file_name", String.class);
var relativePath = headers.get("file_relativePath", String.class);
return selector(name, relativePath);
}
};
}
There is just enough to do like this:
#Filter(inputChannel = "channelA", outputChannel = "channelB")
public boolean selector(#Header("file_name") String name,
#Header("file_relativePath") String relativePath) {
See docs for that #Filter:
* Indicates that a method is capable of playing the role of a Message Filter.
* <p>
* A method annotated with #Filter may accept a parameter of type
* {#link org.springframework.messaging.Message} or of the expected
* Message payload's type. Any type conversion supported by default or any
* Converters registered with the "integrationConversionService" bean will be
* applied to the Message payload if necessary. Header values can also be passed
* as Message parameters by using the
* {#link org.springframework.messaging.handler.annotation.Header #Header} parameter annotation.
* <p>
* The return type of the annotated method must be a boolean (or Boolean).
The #Filter is similar to the #ServiceActivator or #Transformer: you mark the method and point to the channels. The framework creates an endpoint and use that method as a handler to consume messages from the channel. The result of the method call is handler respectively to the endpoint purpose. In case of filter the request message is sent to the output channel (or reply channel from header) if result is true. Otherwise the message is discarded.
See more info in docs: https://docs.spring.io/spring-integration/docs/current/reference/html/configuration.html#annotations

Testing a Multipart file upload Azure Function

So I have written a simple Azure Function (AF) that accepts (via Http Post method) an IFormCollection, loops through the file collection, pushes each file into an Azure Blob storage container and returns the url to each file.
The function itself works perfectly when I do a single file or multiple file post through Postman using the 'multipart/form-data' header. However when I try to post a file through an xUnit test, I get the following error:
System.IO.InvalidDataException : Multipart body length limit 16384 exceeded.
I have searched high and low for a solution, tried different things, namely;
Replicating the request object to be as close as possible to Postmans request.
Playing around with the 'boundary' in the header.
Setting 'RequestFormLimits' on the function.
None of these have helped so far.
The details are the project are as follows:
Azure Function v3: targeting .netcoreapp3.1
Startup.cs
public class Startup : FunctionsStartup
{
public IConfiguration Configuration { get; private set; }
public override void Configure(IFunctionsHostBuilder builder)
{
var x = builder;
InitializeConfiguration(builder);
builder.Services.AddSingleton(Configuration.Get<UploadImagesAppSettings>());
builder.Services.AddLogging();
builder.Services.AddSingleton<IBlobService,BlobService>();
}
private void InitializeConfiguration(IFunctionsHostBuilder builder)
{
var executionContextOptions = builder
.Services
.BuildServiceProvider()
.GetService<IOptions<ExecutionContextOptions>>()
.Value;
Configuration = new ConfigurationBuilder()
.SetBasePath(executionContextOptions.AppDirectory)
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.Development.json", optional: true)
.AddEnvironmentVariables()
.Build();
}
}
UploadImages.cs
public class UploadImages
{
private readonly IBlobService BlobService;
public UploadImages(IBlobService blobService)
{
BlobService = blobService;
}
[FunctionName("UploadImages")]
[RequestFormLimits(ValueLengthLimit = int.MaxValue,
MultipartBodyLengthLimit = 60000000, ValueCountLimit = 10)]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "images")] HttpRequest req)
{
List<Uri> returnUris = new List<Uri>();
if (req.ContentLength == 0)
{
string badResponseMessage = $"Request has no content";
return new BadRequestObjectResult(badResponseMessage);
}
if (req.ContentType.Contains("multipart/form-data") && req.Form.Files.Count > 0)
{
foreach (var file in req.Form.Files)
{
if (!file.IsValidImage())
{
string badResponseMessage = $"{file.FileName} is not a valid/accepted Image file";
return new BadRequestObjectResult(badResponseMessage);
}
var uri = await BlobService.CreateBlobAsync(file);
if (uri == null)
{
return new ObjectResult($"Could not blob the file {file.FileName}.");
}
returnUris.Add(uri);
}
}
if (!returnUris.Any())
{
return new NoContentResult();
}
return new OkObjectResult(returnUris);
}
}
Exception Thrown:
The below exception is thrown at the second if statement above, when it tries to process req.Form.Files.Count > 0, i.e.
if (req.ContentType.Contains("multipart/form-data") && req.Form.Files.Count > 0) {}
Message:
System.IO.InvalidDataException : Multipart body length limit 16384 exceeded.
Stack Trace:
MultipartReaderStream.UpdatePosition(Int32 read)
MultipartReaderStream.ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
StreamHelperExtensions.DrainAsync(Stream stream, ArrayPool`1 bytePool, Nullable`1 limit, CancellationToken cancellationToken)
MultipartReader.ReadNextSectionAsync(CancellationToken cancellationToken)
FormFeature.InnerReadFormAsync(CancellationToken cancellationToken)
FormFeature.ReadForm()
DefaultHttpRequest.get_Form()
UploadImages.Run(HttpRequest req) line 42
UploadImagesTests.HttpTrigger_ShouldReturnListOfUploadedUris(String fileNames)
xUnit Test Project: targeting .netcoreapp3.1
Over to the xUnit Test project, basically I am trying to write an integration test. The project references the AF project and has the following classes:
TestHost.cs
public class TestHost
{
public TestHost()
{
var startup = new TestStartup();
var host = new HostBuilder()
.ConfigureWebJobs(startup.Configure)
.ConfigureServices(ReplaceTestOverrides)
.Build();
ServiceProvider = host.Services;
}
public IServiceProvider ServiceProvider { get; }
private void ReplaceTestOverrides(IServiceCollection services)
{
// services.Replace(new ServiceDescriptor(typeof(ServiceToReplace), testImplementation));
}
private class TestStartup : Startup
{
public override void Configure(IFunctionsHostBuilder builder)
{
SetExecutionContextOptions(builder);
base.Configure(builder);
}
private static void SetExecutionContextOptions(IFunctionsHostBuilder builder)
{
builder.Services.Configure<ExecutionContextOptions>(o => o.AppDirectory = Directory.GetCurrentDirectory());
}
}
}
TestCollection.cs
[CollectionDefinition(Name)]
public class TestCollection : ICollectionFixture<TestHost>
{
public const string Name = nameof(TestCollection);
}
HttpRequestFactory.cs: To create Http Post Request
public static class HttpRequestFactory
{
public static DefaultHttpRequest Create(string method, string contentType, Stream body)
{
var request = new DefaultHttpRequest(new DefaultHttpContext());
var contentTypeWithBoundary = new MediaTypeHeaderValue(contentType)
{
Boundary = $"----------------------------{DateTime.Now.Ticks.ToString("x")}"
};
var boundary = MultipartRequestHelper.GetBoundary(
contentTypeWithBoundary, (int)body.Length);
request.Method = method;
request.Headers.Add("Cache-Control", "no-cache");
request.Headers.Add("Content-Type", contentType);
request.ContentType = $"{contentType}; boundary={boundary}";
request.ContentLength = body.Length;
request.Body = body;
return request;
}
private static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary);
if (string.IsNullOrWhiteSpace(boundary.Value))
{
throw new InvalidDataException("Missing content-type boundary.");
}
if (boundary.Length > lengthLimit)
{
throw new InvalidDataException(
$"Multipart boundary length limit {lengthLimit} exceeded.");
}
return boundary.Value;
}
}
The MultipartRequestHelper.cs class is available here
And Finally the Test class:
[Collection(TestCollection.Name)]
public class UploadImagesTests
{
readonly UploadImages UploadImagesFunction;
public UploadImagesTests(TestHost testHost)
{
UploadImagesFunction = new UploadImages(testHost.ServiceProvider.GetRequiredService<IBlobService>());
}
[Theory]
[InlineData("testfile2.jpg")]
public async void HttpTrigger_ShouldReturnListOfUploadedUris(string fileNames)
{
var formFile = GetFormFile(fileNames);
var fileStream = formFile.OpenReadStream();
var request = HttpRequestFactory.Create("POST", "multipart/form-data", fileStream);
var response = (OkObjectResult)await UploadImagesFunction.Run(request);
//fileStream.Close();
Assert.True(response.StatusCode == StatusCodes.Status200OK);
}
private static IFormFile GetFormFile(string fileName)
{
string fileExtension = fileName.Substring(fileName.IndexOf('.') + 1);
string fileNameandPath = GetFilePathWithName(fileName);
IFormFile formFile;
var stream = File.OpenRead(fileNameandPath);
switch (fileExtension)
{
case "jpg":
formFile = new FormFile(stream, 0, stream.Length,
fileName.Substring(0, fileName.IndexOf('.')),
fileName)
{
Headers = new HeaderDictionary(),
ContentType = "image/jpeg"
};
break;
case "png":
formFile = new FormFile(stream, 0, stream.Length,
fileName.Substring(0, fileName.IndexOf('.')),
fileName)
{
Headers = new HeaderDictionary(),
ContentType = "image/png"
};
break;
case "pdf":
formFile = new FormFile(stream, 0, stream.Length,
fileName.Substring(0, fileName.IndexOf('.')),
fileName)
{
Headers = new HeaderDictionary(),
ContentType = "application/pdf"
};
break;
default:
formFile = null;
break;
}
return formFile;
}
private static string GetFilePathWithName(string filename)
{
var outputFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
return $"{outputFolder.Substring(0, outputFolder.IndexOf("bin"))}testfiles\\{filename}";
}
}
The test seems to be hitting the function and req.ContentLength does have a value. Considering this, could it have something to do with the way the File Streams are being managed? Perhaps not the right way?
Any inputs on this would be greatly appreciated.
Thanks
UPDATE 1
As per this post, I have also tried setting the ValueLengthLimit and MultipartBodyLengthLimit in the Startup of the Azure Function and/or the Test Project as opposed to attributes on the Azure Function. The exception then changed to:
"The inner stream position has changed unexpectedly"
Following this, I then set the fileStream position in the test project to SeekOrigin.Begin. I started getting the same error:
"Multipart body length limit 16384 exceeded."
It took me a 50km bike ride and a good nights sleep but I finally figured this one out :-).
The Azure function (AF) accepts an HttpRequest object as a parameter with the name of 'req' i.e.
public async Task Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "images")] HttpRequest req)
The hierarchy of the files object in the HttpRequest object (along with the parameter names) is as follows:
HttpRequest -> req
FormCollection -> Form
FormFileCollection -> Files
This is what the AF accepts and one would access the files collection by using req.Form.Files
In my test case, instead of posting a FormCollection object, I was trying to post a Stream of a file to the Azure Function.
var formFile = GetFormFile(fileNames);
var fileStream = formFile.OpenReadStream();
var request = HttpRequestFactory.Create("POST", "multipart/form-data", fileStream);
As a result of this, req.Form had a Stream value that it could not interpret and the req.Form.Files was raising an exception.
In order to rectify this, I had to do the following:
Revert all changes made as part of UPDATE 1. This means that I removed the 'RequestFormLimits' settings from the Startup file and left them as attributes on the functions Run method.
Instantiate a FormFileCollection object and add the IFormFile to it
Instantiate a FormCollection object using this FormFileCollection as a parameter.
Add the FormCollection to the request object.
To achieve the above, I had to make the following changes in code.
Change 'Create' method in the HttpRequestFactory
public static DefaultHttpRequest Create(string method, string contentType, FormCollection formCollection)
{
var request = new DefaultHttpRequest(new DefaultHttpContext());
var boundary = $"----------------------------{DateTime.Now.Ticks.ToString("x")}";
request.Method = method;
request.Headers.Add("Cache-Control", "no-cache");
request.Headers.Add("Content-Type", contentType);
request.ContentType = $"{contentType}; boundary={boundary}";
request.Form = formCollection;
return request;
}
Add a private static GetFormFiles() method
I wrote an additional GetFormFiles() method that calls the existing GetFormFile() method, instantiate a FormFileCollection object and add the IFormFile to it. This method in turn returns a FormFileCollection.
private static FormFileCollection GetFormFiles(string fileNames)
{
var formFileCollection = new FormFileCollection();
foreach (var file in fileNames.Split(','))
{
formFileCollection.Add(GetFormFile(file));
}
return formFileCollection;
}
Change the Testmethod
The test method calls the GetFormFiles() to get a FormFileCollection then
instantiates a FormCollection object using this FormFileCollection as a parameter and then passes the FormCollection object as a parameter to the HttpRequest object instead of passing a Stream.
[Theory]
[InlineData("testfile2.jpg")]
public async void HttpTrigger_ShouldReturnListOfUploadedUris(string fileNames)
{
var formFiles = GetFormFiles(fileNames);
var formCollection = new FormCollection(null, formFiles);
var request = HttpRequestFactory.Create("POST", "multipart/form-data", formCollection);
var response = (OkObjectResult) await UploadImagesFunction.Run(request);
Assert.True(response.StatusCode == StatusCodes.Status200OK);
}
So in the end the issue was not really with the 'RequestFormLimits' but rather with the type of data I was submitting in the POST message.
I hope this answer provides a different perspective to someone that comes across the same error message.
Cheers.

Unable to use RabbitMQ RPC with ServiceStack distributed services.

For the life of me I have been unable to get RPC with RabbitMQ working with temp replyto queues. Below is a simple example derived from this test. I see bunch of exceptions in my output window and the dlq fills up, but the message is never acknowledged.
namespace ConsoleApplication4
{
class Program
{
public static IMessageService CreateMqServer(int retryCount = 1)
{
return new RabbitMqServer { RetryCount = retryCount };
}
static void Main(string[] args)
{
using (var mqServer = CreateMqServer())
{
mqServer.RegisterHandler<HelloIntro>(m =>
new HelloIntroResponse { Result = "Hello, {0}!".Fmt(m.GetBody().Name) });
mqServer.Start();
}
Console.WriteLine("ConsoleAppplication4");
Console.ReadKey();
}
}
}
namespace ConsoleApplication5
{
class Program
{
public static IMessageService CreateMqServer(int retryCount = 1)
{
return new RabbitMqServer { RetryCount = retryCount };
}
static void Main(string[] args)
{
using (var mqServer = CreateMqServer())
{
using (var mqClient = mqServer.CreateMessageQueueClient())
{
var replyToMq = mqClient.GetTempQueueName();
mqClient.Publish(new Message<HelloIntro>(new HelloIntro { Name = "World" })
{
ReplyTo = replyToMq
});
IMessage<HelloIntroResponse> responseMsg = mqClient.Get<HelloIntroResponse>(replyToMq);
mqClient.Ack(responseMsg);
}
}
Console.WriteLine("ConsoleAppplication5");
Console.ReadKey();
}
}
}
First exception
RabbitMQ.Client.Exceptions.OperationInterruptedException occurred
_HResult=-2146233088
_message=The AMQP operation was interrupted: AMQP close-reason, initiated by Peer, code=405, text="RESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'mq:tmp:10dd20804ee546d6bf5a3512f66143ec' in vhost '/'", classId=50, methodId=20, cause=
HResult=-2146233088
IsTransient=false
Message=The AMQP operation was interrupted: AMQP close-reason, initiated by Peer, code=405, text="RESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'mq:tmp:10dd20804ee546d6bf5a3512f66143ec' in vhost '/'", classId=50, methodId=20, cause=
Source=RabbitMQ.Client
StackTrace:
at RabbitMQ.Client.Impl.SimpleBlockingRpcContinuation.GetReply()
at RabbitMQ.Client.Impl.ModelBase.ModelRpc(MethodBase method, ContentHeaderBase header, Byte[] body)
at RabbitMQ.Client.Framing.Impl.v0_9_1.Model._Private_QueueBind(String queue, String exchange, String routingKey, Boolean nowait, IDictionary`2 arguments)
at RabbitMQ.Client.Impl.ModelBase.QueueBind(String queue, String exchange, String routingKey, IDictionary`2 arguments)
at RabbitMQ.Client.Impl.ModelBase.QueueBind(String queue, String exchange, String routingKey)
at ServiceStack.RabbitMq.RabbitMqExtensions.RegisterQueue(IModel channel, String queueName)
at ServiceStack.RabbitMq.RabbitMqExtensions.RegisterQueueByName(IModel channel, String queueName)
at ServiceStack.RabbitMq.RabbitMqProducer.PublishMessage(String exchange, String routingKey, IBasicProperties basicProperties, Byte[] body)
InnerException:
followed by this one
System.Threading.ThreadInterruptedException occurred
_HResult=-2146233063
_message=Thread was interrupted from a waiting state.
HResult=-2146233063
IsTransient=true
Message=Thread was interrupted from a waiting state.
Source=mscorlib
StackTrace:
at System.Threading.Monitor.ObjWait(Boolean exitContext, Int32 millisecondsTimeout, Object obj)
at System.Threading.Monitor.Wait(Object obj, Int32 millisecondsTimeout, Boolean exitContext)
InnerException:
Then it repeat for a number of times and hangs. This particular post seems to suggest that they were able to achieve some sort of success with ServerStack and RabbitMQ RPC, but before I start changing my code I'd like to know the reason that my code doesn't work.
Thank you,
Stephen
When your client call GetTempQueueName(), it creates an exclusive queue, which cannot be accessed from another connection (i.e. your server).
Therefore I created my own simple mq-client which does not use servicestack's mq client and only depends on rabbitmq's .net-library:
public class MqClient : IDisposable
{
ConnectionFactory factory = new ConnectionFactory()
{
HostName = "192.168.97.201",
UserName = "guest",
Password = "guest",
//VirtualHost = "test",
Port = AmqpTcpEndpoint.UseDefaultPort,
};
private IConnection connection;
private string exchangeName;
public MqClient(string defaultExchange)
{
this.exchangeName = defaultExchange;
this.connection = factory.CreateConnection();
}
public TResponse RpcCall<TResponse>(IReturn<TResponse> reqDto, string exchange = null)
{
using (var channel = connection.CreateModel())
{
string inq_queue_name = string.Format("mq:{0}.inq", reqDto.GetType().Name);
string responseQueueName = channel.QueueDeclare("",false,false,true,null).QueueName;
//string responseQueueName = channel.QueueDeclare().QueueName;
var props = channel.CreateBasicProperties();
props.ReplyTo = responseQueueName;
var message = ServiceStack.Text.JsonSerializer.SerializeToString(reqDto);
channel.BasicPublish(exchange ?? this.exchangeName, inq_queue_name, props, UTF8Encoding.UTF8.GetBytes(message));
var consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume(responseQueueName, true, consumer);
var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
//channel.BasicAck(ea.DeliveryTag, false);
string response = UTF8Encoding.UTF8.GetString(ea.Body);
string responseType = ea.BasicProperties.Type;
Console.WriteLine(" [x] New Message of Type '{1}' Received:{2}{0}", response, responseType, Environment.NewLine);
return ServiceStack.Text.JsonSerializer.DeserializeFromString<TResponse>(response);
}
}
~MqClient()
{
this.Dispose();
}
public void Dispose()
{
if (connection != null)
{
this.connection.Dispose();
this.connection = null;
}
}
}
It can be used like that:
using (var mqClient = new MqClient("mx.servicestack"))
{
var pingResponse = mqClient.RpcCall<PingResponse>(new Ping { });
}
Important: You've got to use servicestack version 4.0.32+.
There was an issue with redeclaring an exclusive queue which is no longer being done in this commit.
There's also a new RabbitMqTest project showcasing a simple working Client/Server example communicating via 2 independent Console Applications.
This change is available from v4.0.34+ that's now on MyGet.
The ServiceStack.RabbitMq package RabbitMq.Client NuGet dependency has also been upgraded to v3.4.0.

How to log ServiceStack.Messaging.Message to a database with OrmLite?

Given the following code:
public class AppHost : BasicAppHost
{
public AppHost()
: base(typeof(LeadService).Assembly){}
public override void Configure(Container container)
{
SetConfig(new HostConfig
{
DebugMode = ConfigUtils.GetAppSetting<bool>("DebugMode:Enabled", false)
});
//DataAccess
//Set ORMLite to work with columns like ColumnLikeThis
PostgreSqlDialect.Provider.NamingStrategy = new OrmLiteNamingStrategyBase();
//Set ORMLite to use ServiceStack.Text for JSON serialization
PostgreSqlDialect.Provider.StringSerializer = new JsonStringSerializer();
var dbFactory = new OrmLiteConnectionFactory(ConfigUtils.GetConnectionString("Lead:Default"), PostgreSQLDialectProvider.Instance);
container.Register<IDbConnectionFactory>(dbFactory);
//RabbitMQ
container.Register<IMessageService>(c => new RabbitMqServer()
{
AutoReconnect = true,
DisablePriorityQueues = true,
});
var mqServer = container.Resolve<IMessageService>();
//Handlers
container.Register<IMessageHandlers>(c => new MessageHandlers(c.Resolve<IDbConnectionFactory>()));
var handlers = container.Resolve<IMessageHandlers>();
mqServer.RegisterHandler<LeadInformation>(handlers.OnProcessLeadInformation, handlers.OnExceptionLeadInformation);
mqServer.Start();
}
}
public class MessageHandlers : IMessageHandlers
{
private readonly ILog _log = LogManager.GetLogger(typeof(MessageHandlers));
private readonly IDbConnectionFactory _connectionFactory;
public MessageHandlers(IDbConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory;
}
public object OnProcessLeadInformation(IMessage<LeadInformation> request)
{
var sw = Stopwatch.StartNew();
try
{
// Log to the database
using (var db = _connectionFactory.OpenDbConnection())
{
db.CreateTableIfNotExists<Message>();
var msg = request as Message<LeadInformation>; // Anyway not to have to cast it?
db.Save(msg); // Does not work
}
// Run rules against lead
// Log response to database
// return response
}
catch (Exception exception)
{
_log.Error(request, exception);
}
return new LeadInformationResponse
{
TimeTakenMs = sw.ElapsedMilliseconds,
Result = "Processed lead {0}".Fmt(request.GetBody().LeadApplication.LastName)
};
}
public void OnExceptionLeadInformation(IMessage<LeadInformation> request, Exception exception)
{
_log.Error(request, exception);
}
}
Is it possible to persist the whole message? The table gets created, and I was able to save one message, and that's it no more saves with different messages.
Update
Turns out I'm getting an exception during the save operation
Npgsql.NpgsqlException was caught
_HResult=-2147467259
_message=ERROR: 42P01: relation "Message1" does not exist
HResult=-2147467259
IsTransient=false
Message=ERROR: 42P01: relation "Message1" does not exist
Source=Npgsql
ErrorCode=-2147467259
BaseMessage=relation "Message1" does not exist
Code=42P01
ColumnName=""
ConstraintName=""
DataTypeName=""
Detail=""
ErrorSql=SELECT "Id", "CreatedDate", "Priority", "RetryAttempts", "ReplyId", "ReplyTo", "Options", "Error", "Tag", "Body" FROM "Message1" WHERE "Id" = (('ab297bca-5aea-4886-b09b-5a606b0764d5')::uuid)
File=src\backend\parser\parse_relation.c
Hint=""
Line=986
Position=119
Routine=parserOpenTable
SchemaName=""
Severity=ERROR
TableName=""
Where=""
StackTrace:
at Npgsql.NpgsqlState.d__0.MoveNext()
at Npgsql.ForwardsOnlyDataReader.GetNextResponseObject(Boolean cleanup)
at Npgsql.ForwardsOnlyDataReader.GetNextRowDescription()
at Npgsql.ForwardsOnlyDataReader.NextResultInternal()
at Npgsql.ForwardsOnlyDataReader..ctor(IEnumerable1 dataEnumeration, CommandBehavior behavior, NpgsqlCommand command, NotificationThreadBlock threadBlock, Boolean preparedStatement, NpgsqlRowDescription rowDescription)
at Npgsql.NpgsqlCommand.GetReader(CommandBehavior cb)
at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior cb)
at Npgsql.NpgsqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader()
at ServiceStack.OrmLite.OrmLiteReadExtensions.ExecReader(IDbCommand dbCmd, String sql)
at ServiceStack.OrmLite.OrmLiteResultsFilterExtensions.ConvertTo[T](IDbCommand dbCmd, String sql)
at ServiceStack.OrmLite.OrmLiteReadExtensions.SingleById[T](IDbCommand dbCmd, Object value)
at ServiceStack.OrmLite.OrmLiteWriteExtensions.Save[T](IDbCommand dbCmd, T obj)
at ServiceStack.OrmLite.OrmLiteWriteConnectionExtensions.<>c__DisplayClass5a1.b__58(IDbCommand dbCmd)
at ServiceStack.OrmLite.OrmLiteExecFilter.Exec[T](IDbConnection dbConn, Func2 filter)
at ServiceStack.OrmLite.ReadConnectionExtensions.Exec[T](IDbConnection dbConn, Func2 filter)
at ServiceStack.OrmLite.OrmLiteWriteConnectionExtensions.Save[T](IDbConnection dbConn, T obj, Boolean references)
at LO.Leads.Processor.ServiceInterface.MessageHandlers.OnProcessLeadInformation(IMessage`1 request) in e:\Lead\src\LO.Leads.Processor\LO.Leads.Processor.ServiceInterface\MessageHandlers.cs:line 41
Update 2
Turns out my cast was wrong, this now works
using (var db = _connectionFactory.OpenDbConnection())
{
db.CreateTableIfNotExists<Message>();
db.Save(request as Message);
}
Thank you,
Stephen
You have to cast the IMessage back to a Message DTO in order for it to work. e.g.
using (var db = _connectionFactory.OpenDbConnection())
{
db.CreateTableIfNotExists<Message>();
db.Save(request as Message);
}

ASP.NET MVC render Razor Partial View as String Error?

I am using this code to try and render a razor partial view as a string for the purposes of sending an email.
public static string RenderPartialToString(
string userControlPath,
object viewModel,
ControllerContext controllerContext,
TempDataDictionary tempData)
{
using (var writer = new StringWriter())
{
var viewDataDictionary = new ViewDataDictionary(viewModel);
var view = new WebFormView(controllerContext, userControlPath);
var viewContext = new ViewContext(
controllerContext,
view,
viewDataDictionary,
tempData,
writer
);
viewContext.View.Render(viewContext, writer);
return writer.GetStringBuilder().ToString();
}
}
The problem is that I get the follow error:
must derive from ViewPage, ViewPage<TModel>, ViewUserControl, or ViewUserControl<TModel>. Stack Trace: at System.Web.Mvc.WebFormView.RenderView(ViewContext viewContext, TextWriter writer, Object instance) at .... RenderPartialToString
How would I fix that ?
Indeed, WebFormView doesn't inherit from the mentioned classes, just IView. I did a little Google research and got a prototype working. This page was the most helpful.
I created an empty MVC3 application and created the following HomeController. When I run the application, the page shows the rendered string. The resultAsString variable shows how to capture the rendering as a string.
using System;
using System.IO;
using System.Web.Mvc;
public class HomeController : Controller
{
public ActionResult Index()
{
var result = RenderPartial(this.ControllerContext, "This is #DateTime.Now right now");
var resultAsString = result.Content;
return result;
}
private ContentResult RenderPartial(ControllerContext controllerContext, string template)
{
var temporaryViewPath = string.Format("~/Views/{0}.cshtml", Guid.NewGuid());
using (var stringWriter = new StringWriter())
{
using (var fileStream = System.IO.File.Create(Server.MapPath(temporaryViewPath)))
{
using (var streamWriter = new StreamWriter(fileStream))
{
streamWriter.WriteLine(template);
streamWriter.Close();
}
fileStream.Close();
}
var razor = new RazorView(controllerContext, temporaryViewPath, null, false, null);
razor.Render(new ViewContext(controllerContext, razor, new ViewDataDictionary(), new TempDataDictionary(), stringWriter), stringWriter);
System.IO.File.Delete(Server.MapPath(temporaryViewPath));
return Content(stringWriter.ToString());
}
}
}

Resources