Data synchronization (validation) between two clients - c#

I have a webshop built on ASP.NET Boilerplate (Angular frontend and MSSQL database).
The webshop contains items and I want to keep an inventory of these items.
Every time an order is created the inventory is updated. So basically I have a database with Webshops, Items and Orders.
I have repositories and managers for these objects.
All works fine, but the issue occurs when two clients at the same time load the webshop.
Client1 opens webpage:
Webshop1
Item1: "10 items available"
Item2: "8 items available"
Client2 opens webpage at the same time:
Webshop1
Item1: "10 items available"
Item2: "8 items available"
The first one that buys all the available items, should be able to create an order and the second one should get an error.
When an order is created, the backend checks if there are enough items available.
But when the webshop is loaded BEFORE the order of the first client is created, the second client does not know the updated inventory and will be able to create the order as well.
Meaning 20 items of Item1 can be sold!
How do I "sync" the data between the two sessions in the backend? It seems somehow the data is cached in the backend when loading the webshop.
CreateOrder function
public async Task<CreateOrderResponseDto> Create(CreateOrderDto input, long? userId)
{
input.OrderItems.ForEach(async o =>
{
if (!(await _salesItemManager.ReserveStock(o.SalesItemId, o.Quantity)).IsSuccess)
{
throw CodeException.ToAbpValidationException("OrderItem", "OrderItemCreate");
}
});
var salesPage = await _salesPageManager.Get(input.SalesPageId, false);
if (salesPage.GetState() != StatePage.Published)
{
throw CodeException.ToAbpValidationException("Order", "PageNotAvailable");
}
if (salesPage.CommentsRequired.HasValue && salesPage.CommentsRequired.Value)
{
if (string.IsNullOrWhiteSpace(input.Description))
{
throw CodeException.ToAbpValidationException("Order", "CommentsRequired");
}
}
var order = new Order
{
Address = input.Address,
City = input.City,
LastName = input.LastName,
Name = input.Name,
PostalCode = input.PostalCode,
Email = input.Email,
PhoneNumber = input.PhoneNumber,
Description = input.Description,
SalesPage = salesPage
};
try
{
order.Price = await _salesItemManager.GetPriceByOrders(input.OrderItems);
order = await _orderRepository.InsertAsync(order);
input.OrderItems.ForEach(async o =>
{
var orderItem = new OrderItem();
orderItem.SalesItemId = o.SalesItemId;
orderItem.OrderId = order.Id;
orderItem.Quantity = o.Quantity;
await _orderItemRepository.InsertAsync(orderItem);
});
if (input.SelectedSalesPageOptionId.HasValue)
{
order.SalesOption = await _salesPageManager.GetOption(input.SelectedSalesPageOptionId.Value);
}
}
catch (Exception e)
{
throw CodeException.ToAbpValidationException("OrderItem", "OrderItemCreate");
}
if (userId.HasValue && salesPage.User.Id == userId.Value)
{
var payment = await _paymentManager.CreateManualPayment(order, input.IsPaid);
order.Payment = payment;
return new CreateOrderResponseDto() { IsSuccess = true, PaymentUrl = string.Empty, OrderId = order.Id.ToString() };
}
else
{
var payment = await _paymentManager.CreatePayment(order);
order.Payment = payment;
return new CreateOrderResponseDto() { IsSuccess = true, PaymentUrl = payment.PaymentUrl, OrderId = order.Id.ToString() };
}
}
ReserveStock function
public async Task<GeneralDto> ReserveStock(Guid itemId, int quantity)
{
var salesItem = await _salesItemRepository.GetAsync(itemId);
if (salesItem == null || salesItem.Stock == null || salesItem.ReservedStock == null)
return new GeneralDto() { IsSuccess = false };
if (salesItem.Stock < quantity)
{
return new GeneralDto() { IsSuccess = false };
}
salesItem.Stock -= quantity;
salesItem.ReservedStock += quantity;
try
{
await _salesItemRepository.UpdateAsync(salesItem);
}
catch (Exception e)
{
throw CodeException.ToAbpValidationException("SalesItem", "SalesItemUpdate");
}
return new GeneralDto() { IsSuccess = true };
}

The problem is not that the data is cached.
The problem is that your ReserveStock checks are async tasks that are not awaited by the caller:
input.OrderItems.ForEach(async o =>
{
if (!(await _salesItemManager.ReserveStock(o.SalesItemId, o.Quantity)).IsSuccess)
{
throw CodeException.ToAbpValidationException("OrderItem", "OrderItemCreate");
}
});
Assign your async ReserveStock checks to an array of tasks that is awaited by the caller:
var reserveStockChecks = input.OrderItems.Select(async o =>
{
if (!(await _salesItemManager.ReserveStock(o.SalesItemId, o.Quantity)).IsSuccess)
{
throw CodeException.ToAbpValidationException("OrderItem", "OrderItemCreate");
}
}).ToArray();
await Task.WhenAll(reserveStockChecks);

Related

How to check if payment was successful or not Stripe ASP.NET

public ActionResult Index()
{
StripeConfiguration.ApiKey = "secretKey";
var options = new SessionCreateOptions
{
PaymentMethodTypes = new List<string> {
"card",
},
LineItems = new List<SessionLineItemOptions> {
new SessionLineItemOptions {
Name = "Basic Plan",
Description = "Up to 1 Featured Product",
Amount = 1000,
Currency = "usd",
Quantity = 1,
},
},
SuccessUrl = "https://localhost:44390/payment/successful",
CancelUrl = "https://localhost:44390/payment/fail",
PaymentIntentData = new SessionPaymentIntentDataOptions
{
Metadata = new Dictionary<string, string>
{
{"Order_ID", "123456" },
{"Description", "Basic Plan" }
}
}
};
var service = new SessionService();
Session session = service.Create(options);
return PartialView("_PaymentIndex", session);
}
Is there any way of checking the status of the payment, because I wanna call another action if the payment is confirmed. Like the same as adding a successful url, but for an action. I'm kinda new to this so :/
Edit:
I added a webhook to https://xxxx.eu.ngrok.io/payment/UpdatePaymentStatus for this action:
[HttpPost]
public ActionResult UpdatePaymentStatus()
{
try
{
StripeConfiguration.ApiKey = "key";
Stream req = Request.InputStream;
req.Seek(0, System.IO.SeekOrigin.Begin);
string json = new StreamReader(req).ReadToEnd();
myLogger.GetInstance().Warning(User.Identity.Name, "| Check |", "Payment/UpdatePaymentStatus");
// Get all Stripe events.
var stripeEvent = EventUtility.ParseEvent(json);
string stripeJson = stripeEvent.Data.RawObject + string.Empty;
var childData = Charge.FromJson(stripeJson);
var metadata = childData.Metadata;
int orderID = -1;
string strOrderID = string.Empty;
if (metadata.TryGetValue("Order_ID", out strOrderID))
{
int.TryParse(strOrderID, out orderID);
// Find your order from database.
// For example:
// Order order = db.Order.FirstOrDefault(x=>x.ID == orderID);
}
switch (stripeEvent.Type)
{
case Events.ChargeCaptured:
case Events.ChargeFailed:
case Events.ChargePending:
case Events.ChargeRefunded:
case Events.ChargeSucceeded:
case Events.ChargeUpdated:
var charge = Charge.FromJson(stripeJson);
string amountBuyer = ((double)childData.Amount / 100.0).ToString();
if (childData.BalanceTransactionId != null)
{
long transactionAmount = 0;
long transactionFee = 0;
if (childData.BalanceTransactionId != null)
{
// Get transaction fee.
var balanceService = new BalanceTransactionService();
BalanceTransaction transaction = balanceService.Get(childData.BalanceTransactionId);
transactionAmount = transaction.Amount;
transactionFee = transaction.Fee;
}
// My status, it is defined in my system.
int status = 0; // Wait
double transactionRefund = 0;
// Set order status.
if (stripeEvent.Type == Events.ChargeFailed)
{
status = -1; // Failed
myLogger.GetInstance().Warning(User.Identity.Name, "| Purchase of basic plan failed |", "Payment/UpdatePaymentStatus");
}
if (stripeEvent.Type == Events.ChargePending)
{
status = -2; // Pending
myLogger.GetInstance().Warning(User.Identity.Name, "| Purchase of basic plan pending |", "Payment/UpdatePaymentStatus");
}
if (stripeEvent.Type == Events.ChargeRefunded)
{
status = -3; // Refunded
transactionRefund = ((double)childData.AmountRefunded / 100.0);
myLogger.GetInstance().Warning(User.Identity.Name, "| Purchase of basic plan refunded |", "Payment/UpdatePaymentStatus");
}
if (stripeEvent.Type == Events.ChargeSucceeded)
{
status = 1; // Completed
myLogger.GetInstance().Info(User.Identity.Name, "Bought Basic Plan", "Payment/UpdatePaymentStatus");
}
// Update data
// For example: database
// order.Status = status;
// db.SaveChanges();
}
break;
default:
//log.Warn("");
break;
}
return Json(new
{
Code = -1,
Message = "Update failed."
});
}
catch (Exception e)
{
//log.Error("UpdatePaymentStatus: " + e.Message);
return Json(new
{
Code = -100,
Message = "Error."
});
}
}
public ActionResult Successful()
{
return View();
}
public ActionResult Fail()
{
return View();
}
}
Still not working though. I'm not getting any logs(using nLog) and the webhook is failing(in stripe dashboard).
Your code is creating a Checkout Session via the API. This is associated with Stripe's Checkout product. Stripe controls the UI to collect all the payment method details which means you're going to redirect your customer directly to Stripe's hosted page.
Once a customer pays, they will be redirected to your payment page configured in SuccessUrl in your code. You need to write code that detects the customer hitting that URL and map it to the session they were paying.
Additionally, it's possible for a customer to close their browser too before hitting your page. For that reason, Stripe also sends the checkout.session.completed event to your webhook handler. You can write code to handle that event and look at the status of their payment and all relevant information.
Stripe covers all possible fulfillment approaches in their documentation: https://stripe.com/docs/payments/checkout/fulfill-orders

Discord.net issues creating, adding and removing roles

so I'm working on a mute and unmute command and what I want it to do is find if there is a role called "Muted" in the server if there is then give the user that role if there isn't then create the role with the necessary permissions. I've tried messing with bot permissions, role permissions, and hierarchy and it just doesn't do anything. There is no error given to me via Console nor is there an error generated in the text, it just simply seems to do nothing no matter what I try, can anyone see what I'm doing wrong? I created a pre-existing role called "Muted" and even with the role pre-applied it didn't add it. It also doesn't work while trying to remove the role if I manually added it to the user. This is what I've got:
[Command("mute")]
[Remarks("Mutes A User")]
[RequireUserPermission(GuildPermission.MuteMembers)]
public async Task Mute(SocketGuildUser user)
{
var UserCheck = Context.Guild.GetUser(Context.User.Id);
if (!UserCheck.GuildPermissions.MuteMembers)
{
await Context.Message.Channel.SendMessageAsync("", false, new EmbedBuilder()
{
Color = Color.LightOrange,
Title = "You dont have Permission!",
Description = $"Sorry, {Context.Message.Author.Mention} but you do not have permission to use this command",
Author = new EmbedAuthorBuilder()
{
Name = Context.Message.Author.ToString(),
IconUrl = Context.Message.Author.GetAvatarUrl(),
Url = Context.Message.GetJumpUrl()
}
}.Build());
}
else
{
await Context.Guild.GetUser(user.Id).ModifyAsync(x => x.Mute = true);
var muteRole = await GetMuteRole(user.Guild);
if (!user.Roles.Any(r => r.Id == muteRole.Id))
await user.AddRoleAsync(muteRole);//.ConfigureAwait(false);
}
}
[Command("unmute")]
[Remarks("Unmutes A User")]
[RequireUserPermission(GuildPermission.MuteMembers)]
public async Task Unmute(SocketGuildUser user)
{
var UserCheck = Context.Guild.GetUser(Context.User.Id);
if (!UserCheck.GuildPermissions.MuteMembers)
{
await Context.Message.Channel.SendMessageAsync("", false, new EmbedBuilder()
{
Color = Color.LightOrange,
Title = "You dont have Permission!",
Description = $"Sorry, {Context.Message.Author.Mention} but you do not have permission to use this command",
Author = new EmbedAuthorBuilder()
{
Name = Context.Message.Author.ToString(),
IconUrl = Context.Message.Author.GetAvatarUrl(),
Url = Context.Message.GetJumpUrl()
}
}.Build());
}
else
{
await Context.Guild.GetUser(user.Id).ModifyAsync(x => x.Mute = false).ConfigureAwait(false);
try { await user.ModifyAsync(x => x.Mute = false);/*.ConfigureAwait(false); */} catch { ReplyAsync("no"); }
try { await user.RemoveRoleAsync(await GetMuteRole(user.Guild));/*.ConfigureAwait(false); */} catch { ReplyAsync("No lmao"); }
}
}
public async Task<IRole> GetMuteRole(IGuild guild)
{
const string defaultMuteRoleName = "Muted";
var muteRoleName = "Muted";
var muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName);
if (muteRole == null)
{
try
{
muteRole = await guild.CreateRoleAsync(muteRoleName, GuildPermissions.None, Color.Default, false, false);//.ConfigureAwait(false);
}
catch
{
muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName) ?? await guild.CreateRoleAsync(defaultMuteRoleName, GuildPermissions.None, Color.Default, false, false);//.ConfigureAwait(false);
}
}
foreach (var toOverwrite in (await guild.GetTextChannelsAsync()))
{
try
{
if (!toOverwrite.PermissionOverwrites.Any(x => x.TargetId == muteRole.Id && x.TargetType == PermissionTarget.Role))
{
await toOverwrite.AddPermissionOverwriteAsync(muteRole, denyOverwrite);//.ConfigureAwait(false);
await Task.Delay(200);//.ConfigureAwait(false);
}
}
catch
{
}
}
return muteRole;
}
If anyone can help me that would be great, cheers!

Your Campaign is not ready to send in mailchimp api v3 using C#

I created code to send campaign immediately by consuming mailchimp api v3.0 using C# console. When i tried in free account everything goes well, but when i upgrade my account in premium i got this problem (only add 2 members).
My scenario:
create audience => success
add member subscriber into audience that i created => success
create campaign with specific template => success
send cehcklist in campaign already created => return is_ready false
send campaign => return Your Campaign is not ready to send
When I try to run my console program using console c# consume mailchimp api I got this error:
Type: http://developer.mailchimp.com/documentation/mailchimp/guides/error-glossary/
Title: Bad Request
Status: 400
Detail: Your Campaign is not ready to send.
instance: 214b85f4-a288-44e7-b890-35925d8601ac
When I checked campaign into mailchimp website portal, I saw a message like this:
To send, you need at least 1 subscriber in your list.
This means that there is no recipients in my campaign, here is detail screenshot:
Please has anyone ever faced this, too? I really appreciate any suggestions.
Is there any way to resolve that issue before sending campaign? Because when I checked mailchimp portal (based on screenshot shown above), I back into campaign list then open my campaign above the problem automatically resolve, this is my confusing one.
Because mailchimp api v3.0 has limitation only 500 members subscriber on one call, finally I created class to partition my list:
//partition list of members more than 500
public static class Extensions
{
public static List<List<T>> SplitPartition<T>(this IEnumerable<T> collection, int size)
{
var chunks = new List<List<T>>();
var count = 0;
var temp = new List<T>();
foreach (var element in collection)
{
if (count++ == size)
{
chunks.Add(temp);
temp = new List<T>();
count = 1;
}
temp.Add(element);
}
chunks.Add(temp);
return chunks;
}
}
this my main code using several scenario to handle single method can call in many way:
public class MailChimpProcessor
{
static MailChimpProcessor()
{
//initialize
ApiHelper.InitializeClient(baseUrl, apiKey);
}
public class MailChimpResponse
{
public string result { get; set; }
public GlobalErrorResponseModel error { get; set; }
}
public static MailChimpResponse MailChimpSendCampaign(SendEmailCampaignModel model)
{
MailChimpResponse mailchimpResult = new MailChimpResponse();
#region PROPERTY OBJECT AUDIENCE
Contact contact = new Contact()
{
company = model.audience_company,
address1 = model.audience_address1,
address2 = model.address2Config,
city = model.audience_city,
state = model.audience_state,
zip = model.audience_zip,
country = model.audience_country,
phone = model.phoneConfig
};
CampaignDefaults campaign = new CampaignDefaults()
{
from_name = model.campaign_from_name,
from_email = model.campaign_reply_to,
subject = model.campaign_subject,
language = "en"
};
AudienceRequestModel audienceModel = new AudienceRequestModel();
audienceModel.name = model.audience_name;
audienceModel.contact = contact;
audienceModel.permission_reminder = permissionReminderConfig;
audienceModel.use_archive_bar = true;
audienceModel.campaign_defaults = campaign;
audienceModel.notify_on_subscribe = "";
audienceModel.notify_on_unsubscribe = "";
audienceModel.email_type_option = true;
#endregion
#region PROPERTY OBJECT MEMBER
List<Member> members = new List<Member>();
//prevent duplicate email_address
var queryMemberList = model.members.GroupBy(x => x.email_address).Select(x => x.First());
foreach (var item in queryMemberList)
{
members.Add(new Member
{
email_address = item.email_address.ToLower(),
status = "subscribed",
status_if_new = "subscribed",
merge_fields = new MergeFields()
{
FNAME = item.merge_fields.FNAME,
LNAME = item.merge_fields.LNAME
}
});
}
bool isUploadContact = false;
int offset = 0;
const int numberPerBatch = 500; // maximum member per execution.
double LoopMax = Math.Ceiling(members.Count / (double)numberPerBatch);
//partition array
var PartitionMembers = members.SplitPartition(numberPerBatch);
#endregion
//create audience using post method
var audienceResult = AudienceProcessor.PostAudienceAsync(audienceModel).Result;
#region PROPERTY OBJECT CAMPAIGN
Recipients recipient = new Recipients()
{
list_id = audienceResult.ResponseModel != null ? audienceResult.ResponseModel.id : "0"
};
Settings setting = new Settings()
{
subject_line = model.campaign_subject,
title = model.campaign_title,
reply_to = model.campaign_reply_to,
from_name = model.campaign_from_name,
template_id = model.campaign_template_id
};
CampaignRequestModel campaignModel = new CampaignRequestModel();
campaignModel.recipients = recipient;
campaignModel.type = "regular";
campaignModel.settings = setting;
#endregion
if (audienceResult.ResponseModel != null)
{
MemberProcessor.MemberResponse memberResult = new MemberProcessor.MemberResponse();
while (offset < LoopMax)
{
MemberRequestModel memberModel = new MemberRequestModel();
memberModel.members = PartitionMembers[offset];//list based on index of array
memberModel.update_existing = true;
//post contact member
memberResult = MemberProcessor.PostContatcAsync(memberModel, audienceResult.ResponseModel.id).Result;
if (memberResult.ResponseModel != null)
{
isUploadContact = true;
}
else
{
isUploadContact = false;
}
offset++; // increment
}
//create campaign
if (isUploadContact)//belum tereksekusi
{
//sleep thread 20 seconds after upload subcriber members
System.Threading.Thread.Sleep(20000);
//create campaign using post method
var campaignResult = CampaignProcessor.PostCampaignAsync(campaignModel).Result;
if (campaignResult.ResponseModel.id != null)
{
#region USING ITERATION TO CHECK CAMPAIGN
CampaignProcessor.CampaignResponseCheckList campaignChecklist = new CampaignProcessor.CampaignResponseCheckList();
bool isReadySend = false;
int check = 0;
while (check <= 10) //maksimum 10 iteration
{
//check campaign using get method
campaignChecklist = CampaignProcessor.GetCheckListCampaign(campaignResult.ResponseModel.id).Result;
if (campaignChecklist.ResponseModel.is_ready == true) //when error model is not null
{
isReadySend = true;
break;
}
else
{
isReadySend = false;
}
System.Threading.Thread.Sleep(1000); // will puase every 1 second
check++;
}
if (isReadySend)
{
//sleep action before send campaign
System.Threading.Thread.Sleep(2000);
//send campaign
var sendCampaignResult = CampaignProcessor.SendCampaignAsync(campaignResult.ResponseModel.id).Result;
if (sendCampaignResult.ErrorModel == null)
mailchimpResult.result = sendCampaignResult.ResponseModel;
else
mailchimpResult.error = sendCampaignResult.ErrorModel; //i got this return indicate that my campaign is not ready
}
else
{
mailchimpResult.error = campaignChecklist.ErrorModel;
mailchimpResult.result = $"failed Check List Campaign / Your Campaign is not ready to send.";
}
#endregion
}
else
{
mailchimpResult.error = campaignResult.ErrorModel;
mailchimpResult.result = "failed create Campaign";
}
}
else
{
mailchimpResult.result = $"failed create contact: {offset}";
mailchimpResult.error = memberResult.ErrorModel;
}
}
else
{
mailchimpResult.error = audienceResult.ErrorModel;
mailchimpResult.result = "failed create Audience";
}
return mailchimpResult;
}
}
Try this code below
System.Threading.Thread.Sleep(40000); //try change this one
//create campaign using post method
var campaignResult = CampaignProcessor.PostCampaignAsync(campaignModel).Result;
if (campaignResult.ResponseModel.id != null)
{
#region USING ITERATION TO CHECK CAMPAIGN
CampaignProcessor.CampaignResponseCheckList campaignChecklist = new CampaignProcessor.CampaignResponseCheckList();
bool isReadySend = false;
int check = 0;
while (true) //just change this condition
{
//check campaign using get method
campaignChecklist = CampaignProcessor.GetCheckListCampaign(campaignResult.ResponseModel.id).Result;
if (campaignChecklist.ResponseModel.is_ready == true) //when error model is not null
{
isReadySend = true;
break;
}
else
{
isReadySend = false;
}
check++;
}
if (isReadySend)
{
//send campaign
var sendCampaignResult = CampaignProcessor.SendCampaignAsync(campaignResult.ResponseModel.id).Result;
if (sendCampaignResult.ErrorModel == null)
mailchimpResult.result = sendCampaignResult.ResponseModel;
else
mailchimpResult.error = sendCampaignResult.ErrorModel; //i got this return indicate that my campaign is not ready
}
else
{
mailchimpResult.error = campaignChecklist.ErrorModel;
mailchimpResult.result = $"failed Check List Campaign / Your Campaign is not ready to send.";
}
#endregion

API is mixing up data from different devices

I have an API that has devices firing data to it at the same time or within a few milliseconds. What I am finding is that the data is getting mixed up. The data is sent every five minutes (on the clock 05, 10, 15 etc.) I have an execution filter that traps the URL data coming in so I always have a real source, then it goes to the endpoint and then onto processing. For example, there will a be random five minute period missing. When I debug step by step with the missing URL from the execution filter it works fine. By that I mean I take the URL and debug, then it inserts.
In summary, I have device id 1 and device id 2.I will get missing intervals even though, I can see the data has hit the execution filter.
I am assuming that the API is not handling these as separate transactions, but somehow mixing them up together, hence the data missing and the serial numbers appearing in the wrong place, such that data from id 1 is appearing in id 2 vice versa etc.
API End Point:
public class SomeController : ApiController
{
[HttpGet]
[ExecutionFilter]
public async Task<HttpResponseMessage> Get([FromUri] FixedDataModel fdm)
{
var reply = new HttpResponseMessage();
string url = HttpUtility.UrlDecode(HttpContext.Current.Request.QueryString.ToString());
if (url.Contains("timestamp"))
{
reply = TimeSyncValidation.TimeSync;
return reply;
}
else if (!url.Contains("timestamp"))
{
reply = await Task.Run(() => DeviceClass.DeviceApiAsync(fdm, url));
}
return reply;
}
}
Processing class:
namespace API.Services
{
public class DeviceClass
{
private static string serialNumber;
private static byte chk;
private static string channelName, channelReadingNumber, channelValue, queryString, readingDate;
private static int colonPosition, chanCountFrom, equalsPosition;
private static bool checkSumCorrect;
public static HttpResponseMessage DeviceApiAsync(FixedDataModel fdm, string urlQqueryString)
{
Guid guid = Guid.NewGuid();
//ExecutionTrackerHandler.Guid = guid;
//Remove question mark
var q = urlQqueryString;
queryString = q.Substring(0);
var items = HttpUtility.ParseQueryString(queryString);
serialNumber = items["se"];
//Store raw uri for fault finding
var rawUri = new List<RawUriModel>
{
new RawUriModel
{
UniqueId = guid,
RawUri = q,
TimeStamp = DateTime.Now
}
};
//Checksum validation
chk = Convert.ToByte(fdm.chk);
checkSumCorrect = CheckSumValidator.XorCheckSum(queryString, chk);
if (!checkSumCorrect)
{
return ValidationResponseMessage.ResponseHeaders("Checksum");
}
//Create list of items that exist in URL
var urldata = new UrlDataList
{
UrlData = queryString.Split('&').ToList(),
};
var data = new List<UriDataModel>();
//Split the URL string into its parts
foreach (var item in urldata.UrlData)
{
colonPosition = item.IndexOf(":");
chanCountFrom = colonPosition + 1;
equalsPosition = item.LastIndexOf("=");
if (colonPosition == -1)
{
channelName = item.Substring(0, equalsPosition);
channelReadingNumber = "";
channelValue = item.Substring(item.LastIndexOf("=") + 1);
}
else
{
channelName = item.Substring(0, colonPosition);
channelReadingNumber = item.Substring(chanCountFrom, equalsPosition - chanCountFrom);
channelValue = item.Substring(item.LastIndexOf("=") + 1);
if (channelName == "atime" || channelName == "adate")
{
readingDate = DateValidator.CreateDate(channelValue);
}
};
bool nullFlag = false;
if (channelValue == null)
nullFlag = true;
bool missingFlag = false;
if (channelValue == "x") {
missingFlag = true;
channelValue = "0";
}
//Add data to model ready for DB insert.
data.Add(new UriDataModel
{
uid = guid,
SerialNumber = serialNumber,
ChannelName = channelName,
ChannelReadingNumber = channelReadingNumber,
ChannelValue = channelValue.Replace(",", "."),
ReadingDate = readingDate,
TimeStamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm"),
Processed = false,
NullFlag = nullFlag,
MissingFlag = missingFlag
});
};
//Validate dates
var allDates = (from x in data where x.ChannelName.Contains("atime") || x.ChannelName.Contains("adate") select x.ChannelValue).ToList();
bool dateValidation = DateValidator.IsValid(allDates);
if (!dateValidation)
{
return ValidationResponseMessage.ResponseHeaders("Date");
};
//Validate values
var channels = Enum.GetNames(typeof(Channels)).ToList();
List<string> allChannelValues = data.Where(d => channels.Contains(d.ChannelName)).Select(d => d.ChannelValue).ToList();
bool valueValidation = ValueValidator.IsValid(allChannelValues);
if (!valueValidation)
{
return ValidationResponseMessage.ResponseHeaders("Values");
};
//Insert live data
var insertData = DataInsert<UriDataModel>.InsertData(data, "Staging.UriData");
if (!insertData)
{
return ValidationResponseMessage.ResponseHeaders("Sql");
}
var content = "\r\nSUCCESS\r\n";
var reply = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
Content = new StringContent(content)
};
return reply;
}
}
}
TIA
You are using global variables and static method to process your data.
Change your method to non-static.
Each DeviceClass worker must update only its own isolated data then push that off back to controller.

Dynamic change Microsoft BOT Framework Form's Field Step: hiding and showing the confirmation

I'm new to MS BOT Framework.
I changed MS github MultiDialogsBot.sln , I added a HotelsQuery property to init Form's Field value at HotelsDialog.cs,
public class HotelsDialog : IDialog<object>
{
public HotelsQuery _HotelsQuery { get; set; }
public HotelsDialog()
{
_HotelsQuery = new HotelsQuery{
Destination = "Taiwan",
CheckIn = new DateTime(2017,10,29),
Nights = 3
};
}
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Welcome to the Hotels finder!");
var hotelsFormDialog = FormDialog.FromForm(this.BuildHotelsForm, FormOptions.PromptInStart);
context.Call(hotelsFormDialog, this.ResumeAfterHotelsFormDialog);
}
public IForm<HotelsQuery> BuildHotelsForm()
{
OnCompletionAsyncDelegate<HotelsQuery> processHotelsSearch = async (context, state) =>
{
await context.PostAsync($"Ok. Searching for Hotels in {state.Destination} from {state.CheckIn.ToString("MM/dd")} to {state.CheckIn.AddDays(state.Nights).ToString("MM/dd")}...");
};
var destField = new FieldReflector<HotelsQuery>(nameof(HotelsQuery.Destination))
.SetActive((state) =>
{
//depend on _HotelsQuery's values
bool isActive = string.IsNullOrWhiteSpace(_HotelsQuery.Destination);
if (!isActive) state.Destination = _HotelsQuery.Destination;
return isActive;
});
var checkInField = new FieldReflector<HotelsQuery>(nameof(HotelsQuery.CheckIn))
.SetActive((state) =>
{
//depend on _HotelsQuery's values
bool isActive = _HotelsQuery.CheckIn == DateTime.MinValue;
if (!isActive) state.CheckIn = _HotelsQuery.CheckIn;
return isActive;
});
var nightsField = new FieldReflector<HotelsQuery>(nameof(HotelsQuery.Nights))
.SetActive((state) =>
{
//depend on _HotelsQuery's values
bool isActive = _HotelsQuery.Nights == 0;
if (!isActive) state.Nights = _HotelsQuery.Nights;
return isActive;
});
var form = new FormBuilder<HotelsQuery>()
.Field(destField)
.Message("Looking for hotels in {Destination}...")
.Field(checkInField)
.Message("Check in {CheckIn}...")
.Field(nightsField)
.Message("Nights : {Nights}...")
.Confirm("Is this your selection?\n {*}", state =>
{
//clean all fields for showing fields in confirmation
_HotelsQuery.Destination = string.Empty;
_HotelsQuery.CheckIn = DateTime.MinValue;
_HotelsQuery.Nights = 0;
return true;
}, new List<string>())
.Message("Thanks you ...")
.OnCompletion(processHotelsSearch)
.Build();
return form;
}
public async Task ResumeAfterHotelsFormDialog(IDialogContext context, IAwaitable<HotelsQuery> result)
{
try
{
var searchQuery = await result;
var hotels = await this.GetHotelsAsync(searchQuery);
await context.PostAsync($"I found in total {hotels.Count()} hotels for your dates:");
var resultMessage = context.MakeMessage();
resultMessage.AttachmentLayout = AttachmentLayoutTypes.Carousel;
resultMessage.Attachments = new List<Attachment>();
foreach (var hotel in hotels)
{
HeroCard heroCard = new HeroCard()
{
Title = hotel.Name,
Subtitle = $"{hotel.Rating} starts. {hotel.NumberOfReviews} reviews. From ${hotel.PriceStarting} per night.",
Images = new List<CardImage>()
{
new CardImage() { Url = hotel.Image }
},
Buttons = new List<CardAction>()
{
new CardAction()
{
Title = "More details",
Type = ActionTypes.OpenUrl,
Value = $"https://www.bing.com/search?q=hotels+in+" + HttpUtility.UrlEncode(hotel.Location)
}
}
};
resultMessage.Attachments.Add(heroCard.ToAttachment());
}
await context.PostAsync(resultMessage);
}
catch (FormCanceledException ex)
{
string reply;
if (ex.InnerException == null)
{
reply = "You have canceled the operation. Quitting from the HotelsDialog";
}
else
{
reply = $"Oops! Something went wrong :( Technical Details: {ex.InnerException.Message}";
}
await context.PostAsync(reply);
}
finally
{
context.Done<object>(null);
}
}
private async Task<IEnumerable<Hotel>> GetHotelsAsync(HotelsQuery searchQuery)
{
var hotels = new List<Hotel>();
// Filling the hotels results manually just for demo purposes
for (int i = 1; i <= 5; i++)
{
var random = new Random(i);
Hotel hotel = new Hotel()
{
Name = $"{searchQuery.Destination} Hotel {i}",
Location = searchQuery.Destination,
Rating = random.Next(1, 5),
NumberOfReviews = random.Next(0, 5000),
PriceStarting = random.Next(80, 450),
Image = $"https://placeholdit.imgix.net/~text?txtsize=35&txt=Hotel+{i}&w=500&h=260"
};
hotels.Add(hotel);
}
hotels.Sort((h1, h2) => h1.PriceStarting.CompareTo(h2.PriceStarting));
return hotels;
}
}
I have trouble after the confirmation shows. When a user answers yes, BOT will ask CheckIn's prompt.
Why does it not go to the OnCompletion event?
Thanks for your help.
You are clearing out the values in the .Confirm
Try something like this:
var form = new FormBuilder<HotelsQuery>()
.Field(destField)
.Message("Looking for hotels in {Destination}...")
.Field(checkInField)
.Message("Check in {CheckIn}...")
.Field(nightsField)
.Message("Nights : {Nights}...")
.Confirm("Is this your selection?\n {*}", state =>
{
if (_HotelsQuery.Destination == string.Empty ||
_HotelsQuery.CheckIn == DateTime.MinValue ||
_HotelsQuery.Nights == 0)
return false;
return true;
}, new List<string>())
.Message("Thanks you ...")
.OnCompletion(processHotelsSearch)
.Build();

Categories