Masstransit: Respond after TransitionTo - masstransit

I am new with Masstransit and looking for an answer.
How to respond right after all activities completed and saga stage was persited?
Is it possible?
For example.
In Controller:
private readonly IRequestClient<CreateDataRequest> client;
private readonly TestDbContext db; // dbContext from saga
[HttpPost]
public async Task<IActionResult> CreateData(CreateData model)
{
var id = Guid.NewGuid();
var response = await client.GetResponse<CreateDataResponse>(new CreateDataRequest(id,
model.UserId));
var state= await db.Data.FirstOrDefaultAsync(s => s.CorrelationId == id);
return Ok(state);
}
In StateMachine:
private EventActivityBinder<ApplicationState, CreateDataRequest> CreateDataReceived
Initially(
When(CreateDataReceived)
.Then(s =>
{
s.Saga.UserId = s.Message.UserId;
})
// I tried like this, but sometimes is controler I get null
// .Respond(x => new CreateDataResponse(x.Message.CorrelationId))
.Request(GetAdditionalData, c => new GetAdditionalDataRequest(c.Message.CorrelationId))
.TransitionTo(GetAdditionalData.Pending)
// here I want to respond CreateDataResponse after CurrentSatge=GetData.Pending persisted
)

You can enable the in-memory outbox (UseInMemoryOutbox()) on the endpoint and messages will not be sent until the saga state has been persisted.

Related

Manage the state in Production in Bot FrameWork v4

I create One Askstate and AskDialog in Bot FrameWork V4 After release in production sometime state variables are not giving correct value. Below is my code for reference.
the Code Is Working Fine in Emulator though it Not Work Correctly in Production. following is code For referance.
// In Dialogbot class
private readonly IStatePropertyAccessor<AskState> _askStateAccessor;
private readonly UserState _userState;
protected readonly ConversationState _conversationState;
// In Dialogbot constructor
_userState = userState ?? throw new ArgumentNullException(nameof(userState));
_askStateAccessor = _userState.CreateProperty<AskState>(nameof(AskState));
Dialogs = new DialogSet(_dialogStateAccessor);
Dialogs.Add(new AskDialog(_askStateAccessor, _spService, _dataStateAccessor, loggerFactory));
// In OnTurnAsync method
var valueAskState = await _askStateAccessor.GetAsync(turnContext, () => new AskState());
if (valueAskState.IsWhatToWhereKeywords != null)
{
// logic
}
await _conversationState.SaveChangesAsync(turnContext);
await _userState.SaveChangesAsync(turnContext);
// In AskDialog
public IStatePropertyAccessor<AskState> UserProfileAccessor { get; }
// In AskDialog's PromptForEntitiesStepAsync method
var askState = await UserProfileAccessor.GetAsync(stepContext.Context);
if(condition)
{
askState.IsWhatToWhereKeywords = keyword; // setting state variable
}
As per below is a screenshot "aaa" which is keyword should be stored into one state variable.
https://i.stack.imgur.com/cJ12v.png
let me know if anyone has solution.
thanks in Advance.

How to subscribe new value in Akavache?

I'm using Akavache's GetAndFetchLatest method and I have created dependency services to communicate with Akavache's method. I'm calling akavache from service layer successfully when i directly reference. For subscribing
MyMod result = null;
var cache = BlobCache.LocalMachine;
var cachedPostsPromise = cache.GetAndFetchLatest(
"mykey",
() => GetInfo(),
offset =>
{
//some condition
});
cachedPostsPromise.Subscribe(subscribedPosts => {
Device.BeginInvokeOnMainThread(() =>
{
//do sothing.
});
});
result = await cachedPostsPromise.FirstOrDefaultAsync();
return result;
It works.But how an I call subscribe on service layer with interface/dependency service?
I think you are new to reactive programming. Understanding the basic principles helps when using Akavache. Maybe this intro helps.
To answer your question, place code like this in your "repository" class:
public override IObservable<MyClass> Get(string key)
{
var cachedObservable = blobCache.GetAndFetchLatest<MyClass>(key,
() => GetFromServerAsync(key));
return cachedObservable ;
}
And in the caller:
private void getNewData()
{
var myClassObservable = myRepository.Get("the key");
myClassObservable.Subscribe(handleNewMyClass);
}
private void handleNewMyClass(MyClass newClass)
{
//handle the new class
}
Note that handleNewMyClass() is called twice:
first with the MyClass from cache
then with the MyClass that was fetched (from the server)
Using this approach you can simply place the repository class in your IoC Container.
Please find the the sample code :
var result = BlobCache.LocalMachine;
var cachedPostsPromise = cache.GetAndFetchLatest(
"mykey",
() => ViewModelLocator.GetInstance<IYourServiceName>().MethodName(),
offset =>
{
//some condition
});
cachedPostsPromise.Subscribe(subscribedPosts => {
Device.BeginInvokeOnMainThread(() =>
{
//Your piece of code
});
});
result = await cachedPostsPromise.FirstOrDefaultAsync();
return result;
Please note the there any anything inside subscribed will be called twice : first set of data will be cache and second set will be freshly fetched from server.You have to manage according.

asp.net core 2.1 tempdata empty with redirecttoaction

I have an asp.net core 2.1 project and I try to use TempData with RedirectToAction but it's always null (without Error)
Here is my ConfigureServices method
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
//services pour l'authentification
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
options.LoginPath = "/Login";
});
//services pour session
services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromMinutes(20);
});
//configuer port https
services.AddHttpsRedirection(options => options.HttpsPort = 443);
Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
ManageDI(services);
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddSessionStateTempDataProvider();
}
I have "app.UseSession();" in my Configure method
and here is my code
[HttpGet]
public async Task< IActionResult> ResetPassword(string query)
{
TempData["test"]= "test";
return RedirectToAction(nameof(Login));
}
[HttpGet]
public IActionResult Login(string returnUrl = null)
{
var b = TempData["test"];
//b is always null when calling ResetPassword action
var model = new Models.Account.LoginModel{
ReturnUrl = returnUrl
};
return View(model);
}
What did I forget please ?
Thanks
It's not entirely clear what the issue is based on the code you've provided, but since you mention that it's null in your ResetPassword action from within your Login action, I'm assuming you're not properly persisting the value.
TempData is just that: temporary data. Once it's been accessed, it is removed. Therefore, when you set b here with its value, that's it - it's gone. If you then try to access it in another action later or even just in the view this action returns, it will be null now.
If you need to get the value, but also keep it around for later, you need to use TempData.Peek:
var b = TempData.Peek("test");

MS Bot saving conversation data to Cosmos DB

I have successfully connected my MS Bot to Azure Cosmos DB. By default the IBotDataStore does not save conversation text. I have worked out a way to this by context.ConversationData.SetValue("message", messageText); for each PostAsync. Can anyone help me with a better way to do this?
Global.asax.cs
var uri = new Uri(ConfigurationManager.AppSettings["DocumentDbUrl"]);
var key = ConfigurationManager.AppSettings["DocumentDbKey"];
var store = new DocumentDbBotDataStore(uri, key);
Conversation.UpdateContainer(
builder =>
{
builder.Register(c => store)
.Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
.AsSelf()
.SingleInstance();
builder.Register(c => new CachingBotDataStore(store, CachingBotDataStoreConsistencyPolicy.ETagBasedConsistency))
.As<IBotDataStore<BotData>>()
.AsSelf()
.InstancePerLifetimeScope();
});
And this is the Greeting Dialog:
Greeting Dialog
using System;
using System.Threading.Tasks;
using System.Net.Http;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
namespace HalChatBot.Dialogs
{
[Serializable]
public class GreetingDialog : IDialog<object>
{
public string userName;
public string messageText;
public async Task StartAsync(IDialogContext context)
{
messageText = "Hi, can I please have your name.";
await context.PostAsync(messageText);
context.ConversationData.SetValue("message", messageText);
context.Wait(GetName);
}
public virtual async Task GetName(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var _result = await result;
userName = _result.Text;
//string message = activity.AsMessageActivity()?.Text;
context.UserData.SetValue("username", userName);
messageText = $"Thanks {userName}, how can I help you?";
await context.PostAsync(messageText);
context.ConversationData.SetValue("message", messageText);
context.Done(context);
}
}
}
Have you checked out the IActivityLogger interface? https://learn.microsoft.com/en-us/azure/bot-service/dotnet/bot-builder-dotnet-middleware Implementing this interface will enable you to save conversation history in whatever message store you choose.
IBotDataStore is not for conversation history, but for UserData, ConversationData and PrivateConversationData state.

httpmessagehandler - reading content

I created a message handler which will log the request and the response. ideally I want to
public class LoggingMessageHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
LogRequest(request);
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
LogResponse(response);
return response;
});
}
private void LogRequest(HttpRequestMessage request)
{
var writer = request.GetConfiguration().Services.GetTraceWriter();
var content = request.Content;
(content ?? new StringContent("")).ReadAsStringAsync().ContinueWith(x =>
{
writer.Trace(request, "request", System.Web.Http.Tracing.TraceLevel.Info, t =>
{
t.Message = x.Result;
});
});
}
private void LogResponse(HttpResponseMessage response)
{
var request = response.RequestMessage;
var writer = request.GetConfiguration().Services.GetTraceWriter();
var content = response.Content;
(content ?? new StringContent("")).ReadAsStringAsync().ContinueWith(x =>
{
writer.Trace(request, "response", System.Web.Http.Tracing.TraceLevel.Info, t =>
{
t.Status = response.StatusCode;
t.Message = x.Result;
});
});
}
}
and here is my client code.
public ActionResult Index()
{
var profile = Client.GetAsync("Vendor").Result.EnsureSuccessStatusCode().Content.ReadAsAsync<VendorProfileModel>().Result;
return View(profile);
}
Logging appears to be working. However, when this handler is registered my client code returns an empty object. If I remove this handler the model is successfully read from the response and displayed on screen.
Is there a way to read the content and display the results on the client?
after a few more days for digging around on the net I finally found the root problem and a solution. First the problem:
everything in webapi is async
my action uses Controller.User which in turn is calling Thread.CurrentPrinciple
I am using ITraceWriter as my logging abstraction
apparently there is a bug in the ITraceWriter mechanicism where the current profile is not propagated across threads. therefore, i loose the principle when i get to my controller action. therefore, my query returns an empty result, rather than a fully populated result.
solution: don't use ITraceWriter to log messages. It would have been nice to use the built in mechanics, but that doesn't work. here is the link to the same issue which provides more detail/context.
https://aspnetwebstack.codeplex.com/workitem/237

Resources