We're using RavenDB saga storage, but the saga data isn't being persisted after the starting message handler. I only see a handful of subscription documents in the database. I'm not sure what to check next. Ideas?
I have a saga:
public class BuyerWaitingOnDocumentsDistributor :
Saga<BuyerDocumentDistributorData>,
IAmStartedByMessages<DocumentVersionRequiresBuyerSignature>
{
public void Handle(DocumentVersionRequiresEmployeeSignature message)
{
Data.DocumentVersionId = message.DocumentVersionId.Value;
// Business logic goes here
}
// Other handlers & methods here
}
and saga entity:
public class BuyerDocumentDistributorData : IContainSagaData
{
public virtual Guid Id { get; set; }
public virtual string Originator { get; set; }
public virtual string OriginalMessageId { get; set; }
public virtual Guid DocumentVersionId { get; set; }
public virtual EmployeeId[] AuthorizedToSign { get; set; }
}
and saga finder:
public class BuyerWaitingOnDocumentsDistributorSagaFinder :
IFindSagas<BuyerDocumentDistributorData>.Using<DocumentVersionRequiresBuyerSignature>,
IFindSagas<BuyerDocumentDistributorData>.Using<DocumentVersionSignedByBuyer>,
IFindSagas<BuyerDocumentDistributorData>.Using<DocumentVersionNoLongerRequiresSignature>
{
private static readonly ILog Log = LogManager.GetLogger(typeof (BuyerWaitingOnDocumentsDistributorSagaFinder));
public ISagaPersister Persister { get; set; }
public BuyerDocumentDistributorData FindBy(DocumentVersionRequiresBuyerSignature message)
{
return Persister.Get<BuyerDocumentDistributorData>("DocumentVersionId", message.DocumentVersionId.Value);
}
public BuyerDocumentDistributorData FindBy(DocumentVersionSignedByBuyer message)
{
return Persister.Get<BuyerDocumentDistributorData>("DocumentVersionId", message.DocumentVersionId.Value);
}
public BuyerDocumentDistributorData FindBy(DocumentVersionNoLongerRequiresSignature message)
{
return Persister.Get<BuyerDocumentDistributorData>("DocumentVersionId", message.DocumentVersionId.Value);
}
}
I've also asked this question on the NServiceBus Yahoo group: http://tech.groups.yahoo.com/group/nservicebus/message/13265
You did not give your endpointconfig, so I'm unsure which container you are using.
We experience the same (saga not getting persisted) when using the NinjectBuilder. Using the DefaultBuilder works ok.
Related
I have an option class which like the following
public class EmailOptions
{
public EmailOptions(IEmailConfiguration account) {
this.Configuration = account;
}
public string DefaultFromAddress { get; set; }
public string DefaultFromDisplayName { get; set; }
public IEmailConfiguration Configuration { get; }
}
The IEmailConfiguration interface is there because in some cases I can have an Smtp library and so I need an Smtp based configuration while in some other cases I can use other services which needs a different configuration. Example:
public class ApiKeyConfiguration : IEmailConfiguration
{
public ApiKeyConfiguration() {
}
public string AccountName { get; set; }
public string AccountKey { get; set; }
}
or
public class SmtpConfiguration : IEmailConfiguration
{
public SmtpConfiguration() {
}
public string Host { get; set; }
public string Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Domain { get; set; }
public bool EnableSsl { get; set; }
public bool UseDefaultCredentials { get; set; }
}
I am sure I am registering the correct implementation with
services.AddTransient<IEmailConfiguration, ApiKeyConfiguration>();
However when I try to inject an IOption<> into a controller I am getting the following error:
[13:56:26 ERR] An unhandled exception has occurred: Cannot create instance of type 'EmailOptions' because it is missing a public parameterless constructor.
System.InvalidOperationException: Cannot create instance of type 'EmailOptions' because it is missing a public parameterless constructor.
at Microsoft.Extensions.Configuration.ConfigurationBinder.CreateInstance(Type type)
Of course I can add a parameterless constructor to the class but how do I ensure that the DI container will create an instance of my class by using the constructor with parameter dependency?
Technically, you may try to create own implementation of IConfigureOptions.
Represents something that configures the TOptions type. Note: These are run before all IPostConfigureOptions.
So do something like this:
public class ConfigureEmailOptions : IConfigureOptions<EmailOptions>
{
private readonly IEmailConfiguration _account;
public ConfigureMyOptions(IEmailConfiguration account)
{
_account = account;
}
public void Configure(EmailOptions options)
{
options.Configuration = _account;
...
}
}
and register it as
services.AddTransient<IConfigureOptions<EmailOptions>, ConfigureEmailOptions>();
and your option class should be just
public class EmailOptions
{
public string DefaultFromAddress { get; set; }
public string DefaultFromDisplayName { get; set; }
public IEmailConfiguration Configuration { get; set; }
}
It's saying you need a public parameterless constructor.
Add
public EmailOptions() {
}
Based on the way configuration works in ASP.NET Core, it will always use a parameterless constructor. Although it's not the only reason why, one particularly good reason is that the configuration setup happens before any DI services are registered, meaning it couldn't inject anything if it wanted to.
Long and short, there's no way to satisfy something like IEmailConfiguration in a strongly-typed configuration class. It's frankly a bad idea anyways. Just let your configuration class be a simple entity and inject that into a service or something that handles your email stuff, instead of the other way around.
I have a saga data class with one property marked by Unique attribute. However, this didn't prevent NServiceBus from creating several sagas with identical values in this field.
Here is my data class:
public class ModuleAliveSagaData : ContainSagaData
{
[Unique]
public string ModuleId { get; set; }
public string Endpoint { get; set; }
public string Module { get; set; }
public DateTime LastCheck { get; set; }
public bool Warning { get; set; }
public bool Error { get; set; }
}
Here is the mapping:
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<ModuleAliveMessage>(m => m.Id).ToSaga(s => s.ModuleId);
}
Here is how data gets its values:
public void Handle(ModuleStartedMessage message)
{
Log.InfoFormat("Module {0} started on {1} at {2}", message.ModuleName, message.Endpoint, message.Timestamp);
Data.ModuleId = message.Id;
Data.Endpoint = message.Endpoint;
Data.Module = message.ModuleName;
Data.LastCheck = DateTime.Now;
Data.Warning = false;
Bus.SendLocal(new SendNotification
{
Subject = string.Format("Module {0} is online at {1}", Data.Module, Data.Endpoint)
});
RequestTimeout<ModuleCheckTimeout>(TimeSpan.FromMinutes(5));
Bus.Publish(new ModuleActivated
{
Endpoint = message.Endpoint,
Module = message.ModuleName
});
}
And here is what I see in the saga persistence table (Azure table storage):
Does it suppose to work like this or may be I am missing something?
Yves wrote this in comments, basically it is the proper answer:
Azure storage cannot check for uniquess besides the partitionkey/rowkey pair, so that attribute is ignored. If you need uniqueness you will have to consider another storage techology. PS: this is a known limitation of the underlying storage: http://github.com/Particular/NServiceBus.Azure/issues/21
I have two saga's namely a client saga and a client billing saga.
public class ClientSagaState:IContainSagaData
{
#region NserviceBus
public Guid Id { get; set; }
public string Originator { get; set; }
public string OriginalMessageId { get; set; }
#endregion
public Guid ClientRef { get; set; }
public ClientMessage ClientChangeMessage { get; set; }
public ClientContactChangeMessage ClientContactChange { get; set; }
}
public class ClientBillingSagaState:IContainSagaData
{
#region NserviceBus
public Guid Id { get; set; }
public string Originator { get; set; }
public string OriginalMessageId { get; set; }
#endregion
public Guid ClientRef { get; set; }
public Guid FunderRef { get; set; }
public Guid ClientBillingRef { get; set; }
public ClientBillingMessage ClientBillingMessage { get; set; }
}
public class ClientSaga:Saga<ClientSagaState>,
IAmStartedByMessages<ClientChangeMessage>,
IAmStartedByMessages<ClientContactChangeMessage>
{
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<ClientChangeMessage>(s => s.ClientRef, m => m.EntityRef);
ConfigureMapping<ClientContactChangeMessage>(s => s.ClientRef, m => m.PrimaryEntityRef);
}
public void Handle(ServiceUserChangeMessage message)
{
if (BusRefTranslator.GetLocalRef(EntityTranslationNames.ClientChange, message.EntityRef.Value) != null)
{
GetHandler<ClientChangeMessage>().Handle(message);
CompleteTheSaga();
return;
}
HandleServiceUserChangeAndDependencies(message);
//MarkAsComplete();
CompleteTheSaga();
}
}
public class ClientBillingSaga:Saga<ClientBillingSagaState>
,IHandleMessages<ClientChangeMessage>,
IAmStartedByMessages<ClientBillingMessage>,
IHandleMessages<FunderChangeMessage>
{
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<ClientChangeMessage>(s => s.ClientRef, m => m.EntityRef);
ConfigureMapping<FunderChangeMessage>(s => s.FunderRef, m => m.EntityRef);
ConfigureMapping<ClientBillingMessage>(s => s.ClientBillingRef, m => m.PrimaryEntityRef);
}
public void Handle(ClientChangeMessage message)
{
var state = this.Data;
if (state.ClientBillingMessage != null)
{
Handle(state.ClientBillingMessage);
}
}
public void Handle(CareSysInvoiceLineInsertMessage message)
{
//First check for the funder
//If funder is not there store the message in saga
//If funder is there then check for client
//If client is not there then put the message in saga
// if funder and client are there then execute the message delete the saga
}
}
Here is the scenario:
1)If i receive a ClientBillingMessage whose client and funder are not there , i store this message in saga.
2)If now the funder message comes then it gets executed , but the saga still persists as it is waiting for client message
3)Now if the client message comes it runs the ClientBillingSaga's client message handler first hence the already existing ClientBillingSaga still persists and after that it goes and executes the handler inside the ClientSaga for Client message.
My question: Is there any way i can order the execution of these two saga. I found ISpecifyMessageHandlerOrdering interface which deals with handling of messages but i don't think i can use it for saga.
Sagas are meant to help with synchronising events over time.
You can collect the data you need form the messages you handled in your saga state and once you you have handled all then proceed to send a command/publish an event so the business logic could be processed by a handler.
Note: The actual work (business logic and domain data manipulation) should be done in a separate handler (to comply with SRP and unit of work).
hope this helps
I think you should not be ordering the execution of two Sagas. Saga should be autonomous acting purely on the messages it receives, has an internal state and either sends out messages or complete. If you really want to interact with another Saga, you should be using messages(commands/events).
My saga below is not handling the ValidateRegistration and ValidateRegistration commands. I see the "Could not find a saga for the message type Registrations.Messages.ValidateRegistration with id ..." message.
Is my configuration to find saga not correct? Please help!
Thanks
PS: I am using the generic host in the registration process and I am using NServiceBus.Lite profile.
public class EndpointConfig : IConfigureThisEndpoint, AsA_Publisher, IWantCustomInitialization
{
#region Implementation of IWantCustomInitialization
public void Init()
{
var kernel = new StandardKernel();
kernel.Load(new BackendModule());
//Configure.Instance.Configurer.ConfigureProperty<RegistrationSaga>(x => x.Factory, kernel.Get<IAggregateRootFactory>());
Configure.With().NinjectBuilder(kernel);
}
#endregion
}
public class RegistrationSagaData : IContainSagaData
{
#region Implementation of ISagaEntity
public virtual Guid Id { get; set; }
public virtual string Originator { get; set; }
public virtual string OriginalMessageId { get; set; }
public virtual RegistrationID RegistrationID { get; set; }
public virtual bool IsValidated { get; set; }
public virtual string RegistrationType { get; set; }
#endregion
}
public class RegistrationSaga : Saga<RegistrationSagaData>,
IAmStartedByMessages<StartRegistration>,
IHandleMessages<ValidateRegistration>,
IHandleMessages<CancelRegistration>
{
public RegistrationFactory Factory { get; set; }
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<StartRegistration>(data => data.RegistrationID, registration => registration.ID);
ConfigureMapping<ValidateRegistration>(data => data.RegistrationID, registration => registration.ID);
ConfigureMapping<CancelRegistration>(data => data.RegistrationID, registration => registration.ID);
}
#region Implementation of IMessageHandler<StartRegistration>
public void Handle(StartRegistration message)
{
Data.IsValidated = false;
Data.RegistrationType = message.RegistrationType;
Bus.SendLocal(new CreateRegistration
{
RegistrationType = message.RegistrationType,
ID = message.ID
});
Console.WriteLine("======> handled StartRegistration");
}
#endregion
#region Implementation of IMessageHandler<ValidateRegistration>
public void Handle(ValidateRegistration message)
{
MarkAsComplete();
Console.WriteLine("======> handled ValidateRegistration");
}
#endregion
#region Implementation of IMessageHandler<CancelRegistration>
public void Handle(CancelRegistration message)
{
Console.WriteLine("======> handled CancelRegistration");
MarkAsComplete();
}
#endregion
}
Your handler for StartRegistration is not adding RegistrationID to the Saga's Data. So your override of ConfigureHowToFindSaga is mapped on a property that's has no value when the other commands are handled.
Just today I heard that Ninject doesn't handle setter injection out of the box - try switching it over to constructor injection and see if that works.
I'm learning WCF, and tried to make a small service that exposes a Project and its tasks (the standard Entity Framework hello world).
The class structure is the following:
public class Project
{
public int ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime CreationDate { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
}
public class Task
{
public int TaskId { get; set; }
public string Title { get; set; }
public virtual Project RelatedProject { get; set; }
}
The DB context comes after:
public class ProjectContext : DbContext
{
public DbSet<Project> Projects { get; set; }
public DbSet<Task> Tasks { get; set; }
}
Finally, the service endpoint:
public IEnumerable<Project> getProjects()
{
ProjectContext p = new ProjectContext();
return p.Projects.AsEnumerable();
}
The problem is that this model will throw a System.ServiceModel.CommunicationException, but, If I remove the virtual properties from the model, It would work, but I would loose the entity framework links between Project and Task.
Anyone with a similar setup?
I banged my head against the wall several hours with this one. After extensive debugging, google gave the answer and I feel right to post it here since this was the first result I got in google.
Add this class on top of your [ServiceContract] interface declaration (typically IProjectService.cs
public class ApplyDataContractResolverAttribute : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
{
}
public void ApplyClientBehavior(OperationDescription description, System.ServiceModel.Dispatcher.ClientOperation proxy)
{
var dataContractSerializerOperationBehavior =
description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver =
new ProxyDataContractResolver();
}
public void ApplyDispatchBehavior(OperationDescription description, System.ServiceModel.Dispatcher.DispatchOperation dispatch)
{
var dataContractSerializerOperationBehavior =
description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver =
new ProxyDataContractResolver();
}
public void Validate(OperationDescription description)
{
// Do validation.
}
}
Requirements are
using System.ServiceModel.Description;
using System.Data.Objects;
using System.ServiceModel.Channels;
Then under the [OperationContract] keyword add [ApplyDataContractResolver] keyword and you are set!
Big thanks to http://blog.rsuter.com/?p=286
For sending data trough WCF you should disable lazy loading (dataContext.ContextOptions.LazyLoadingEnabled = false;).
To be sure the data you want is loaded you need to use eager loading ( trough the Include method).
You need to change your function to:
public IEnumerable<Project> getProjects()
{
ProjectContext p = new ProjectContext();
p.ContextOptions.LazyLoadingEnabled = false;
return p.Projects.Include("Tasks").AsEnumerable();
}