Microsoft Bot Receive attachments from user using FormFlow - c#

I'm Designing a Bot using FormFlow in which one of the input will be asking user to attach a file to proceed further.
I Can see below link addresses the similiar problem.
https://github.com/Microsoft/BotBuilder/issues/570
The solution provided in the link is to use custom IRecognizer or as below
a) Put it into a private field/property that is not exposed to FormFlow.
b) Put it in as the value of field that is exposed to form flow.
c) Use the private property to dynamically generate a field that allows choosing between them.
I'm naive to the Bot Framework. Are there any examples to implement this on receiving the attachment from customer using FormFlow .
Below is my code snippet
enter code here
[Serializable]
public class DocBot
{
[Prompt("What's your name?")]
public string Name { get; set; }
[Prompt("Hey {&} , Choose the options below? {||}")]
public Service? shaun;
[Prompt("Attach the Document required for further processing?")]
public string Document { get; set; }
-- Need Suggestion on receiving documents attachment from the user here
[Prompt("What is your Job Title there?")]
public string JobTitle { get; set; }
[Prompt("What's the best number to contact you on?")]
public string Phone { get; set; }
public enum Service
{
Consultancy, Support, ProjectDelivery, Unknown
}
public static IForm<DocBot> BuildEnquiryForm()
{
return new FormBuilder<DocBot>()
.Message("Welcome to Doc BOT!!!")
.Field(nameof(Name))
// .Field(nameof(Document))
-- Need Suggestion on receiving documents attachment from the user here
.Build();
}
}

Update
Support for attachments in FormFlow was added in https://github.com/Microsoft/BotBuilder/pull/2870
There is a sample located here that demonstrates how to accomplish this. For the form itself, you will need to look at ImagesForm.cs
---------
Currently this is not supported. After going through the BotBuilder code, the only workaround I could offer implies rebuilding the BotBuilder library code as you will have to make some updates in the FormDialog to hack it a bit to get the Attachment url.
If you want to try the workaround (again, is workaround, I haven't fully tested this and this could have other implications that I'm not aware of), get the BotBuilder code, find the FormDialog class and then replace these two lines with the following code:
var message = toBot != null ? (await toBot) : null;
var toBotText = message != null ? message.Text : null;
var toBotAttachments = message != null ? message.Attachments : null;
var stepInput = (toBotAttachments != null && toBotAttachments.Any()) ? toBotAttachments.First().ContentUrl : (toBotText == null) ? "" : toBotText.Trim();
What this workaround does is checking if the incoming message has attachments. If it has, then it discards the text and uses the ContentUrl of the first attachment. Then, in your Form model property, you will get the attachment url.

Related

Reading body in PostRequest asp.net core

I've been trying to read the body of a post request in a controller for a while but I keep getting the error that synchronous actions are not allowed. This is also meant to be used as an API so I can't just read the values trough a textbox like normal.
This is my code right now but I have no idea if this is right because this is based on Java code and my experience with asp.net core is limited.
public IActionResult Test()
{
string body = "";
StreamReader reader = new StreamReader(Request.Body);
while (reader.ReadLine() != null)
{
body += reader.ReadLine();
}
return Ok(body);
}
I'm just trying to read the body so I have an idea on how I can filter on the header of the json input.
Is there maybe a different way that I can do this? Or am I forgetting something important?
Thanks to the advice of Chetan Ranpariya, I found the answer. Like Chetan said I needed to use a model class which is pretty easy to do. I just added a folder named Models and added a class name User with this code:
public class User
{
public string UserName { get; set; }
public string Password { get; set; }
}
And then my previous method turned into this:
public IActionResult Test(User user)
{
return Ok(new { username = user.UserName, password = user.Password });
}
The only thing that I needed to pay attention to was the json body of my request. The header needs to correspond with the property name so it looked like this:
{
"UserName": "Admin",
"Password": "AdminPass"
}
And now I can continue working on my project.

How to save/pass MongoDB UpdateDefinition for logging and later use?

I am stumped on how to save/pass MongoDB UpdateDefinition for logging and later use
I have created general functions for MongoDB in Azure use on a collection for get, insert, delete, update that work well.
The purpose is to be able to have a standard, pre-configured way to interact with the collection. For update especially, the goal is to be able to flexibly pass in an appropriate UpdateDefinition where that business logic is done elsewhere and passed in.
I can create/update/set/combine the UpdateDefinition itself, but when i try to log it by serializing it, it shows null:
JsonConvert.SerializeObject(updateDef)
When I try to log it, save it to another a class or pass it to another function it displays null:
public class Account
{
[BsonElement("AccountId")]
public int AccountId { get; set; }
[BsonElement("Email")]
public string Email { get; set; }
}
var updateBuilder = Builders<Account>.Update;
var updates = new List<UpdateDefinition<Account>>();
//just using one update here for brevity - purpose is there could be 1:many depending on fields updated
updates.Add(updateBuilder.Set(a => a.Email, email));
//Once all the logic and field update determinations are made
var updateDef = updateBuilder.Combine(updates);
//The updateDef does not serialize to string, it displays null when logging.
_logger.LogInformation("{0} - Update Definition: {1}", actionName, JsonConvert.SerializeObject(updateDef));
//Class Created for passing the Account Update Information for Use by update function
public class AccountUpdateInfo
{
[BsonElement("AccountId")]
public int AccountId { get; set; }
[BsonElement("Update")]
public UpdateDefinition<Account> UpdateDef { get; set; }
}
var acct = new AccountUpdateInfo();
acctInfo.UpdateDef = updateDef
//This also logs a null value for the Update Definition field when the class is serialized.
_logger.LogInformation("{0} - AccountUpdateInfo: {1}", actionName, JsonConvert.SerializeObject(acct));
Any thoughts or ideas on what is happening? I am stumped on why I cannot serialize for logging or pass the value in a class around like I would expect
give this a try:
var json = updateDef.Render(
BsonSerializer.SerializerRegistry.GetSerializer<Account>(),
BsonSerializer.SerializerRegistry)
.AsBsonDocument
.ToString();
and to turn a json string back to an update definition (using implicit operator), you can do:
UpdateDefinition<Account> updateDef = json;
this is off the top of my head and untested. the only thing i'm unsure of (without an IDE) is the .Document.ToString() part above.

Send message to Discord server based on input to text boxes?

I am trying to make an application app with C# in Visual Studio. I need some help with this, I want to submit a template like this:
<discord tag>
Link: <link>
Description: <desc>
Any help is useful! Thanks!
Say you want to message a link to an article:
public class Article
{
public string Link { get; set; }
public string Description { get; set; }
}
Then try:
// implement your required logic for getting the tag
string tag = GetTag();
// implement your required logic for getting the reference
Article article = GetLinkWithDescription();
// Prepare the message
string message = string.Join(
"\n",
$"<#{tag}>",
$"Link: {article.Link}",
$"Description: {article.Description}"
);
// send the message
await Context.Channel.SendMessageAsync(message);

Customising ApplicationUser asp.net-identity

I'm trying to add a list of objects the the standard ApplicationUser object in MVC.
I've been looking at dozens of questions all about this but they all seem to be adding a single object, rather than a list.
What I'm trying to do is record all historic passwords a user has used, so I've added a table called AspNetUserPreviousPassword.
In my code I've added this line to the ApplicationUserclass:
public virtual DbSet<AspNetUserPreviousPassword> PreviousUserPasswords { get; set; }
and within the AspNetUserPreviousPassword object I've added the following:
public string ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
I've created the following extensions class:
public static class IdentityExtensions which has appeared in the User.Identity intellisense - so far so good.
{
public static string GetPreviousUserPasswords(this IIdentity identity)
{
var claim = ((ClaimsIdentity)identity).FindFirst("PreviousUserPasswords");
// Test for null to avoid issues during local testing
return (claim != null) ? claim.Value : string.Empty;
}
}
When I went to edit the GenerateUserIdentityAsync function, to insert the custom claims, I began to think my approach was incorrect as you can only add strings, e.g.
userIdentity.AddClaim(new Claim("PreviousUserPasswords", "This must be a string"));
Despite only being able to add a string here, I wanted to test my previous passwords were being read from the database so I added this code to test:
string previousPasswords = "";
await this.PreviousUserPasswords.ForEachAsync(p => previousPasswords += p.PasswordHash);
this.PreviousUserPasswords is always NULL.
My questions:
1) Why is this.PreviousUserPasswords always NULL?
2) Is my approach even correct - can I add a list of objects to ApplicationUser or should I be doing this another way?
Here is the article I used to get preventing the use of previous "X" passwords in my implementation of Asp.Net Identity. I tweaked it to suit my needs but this got me everything I needed to get on the right track. Give it a read.
http://www.c-sharpcorner.com/UploadFile/4b0136/how-to-customize-password-policy-in-Asp-Net-identity/

c# web api request validation filter, context values empty - arguments list isn't

I'm using C# web api and want to create a filter to all requests.
I have a designated class to every request so I just want to add some data annotations and get the validation over with.
The problem is that I'm getting true every time on actionContext.ModelState.IsValid
I have added my filter in the config:
config.Filters.Add(new RequestValidationFilter());
validation method looks like every other in the web
public class RequestValidationFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
var errors = actionContext.ModelState
.Values
.SelectMany(m => m.Errors
.Select(e => e.ErrorMessage));
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
actionContext.Response.ReasonPhrase = string.Join("\n", errors);
}
}
}
I have the following method:
[HttpPost, Route("User/Login")]
public async Task<Responses.Login> Login(Requests.Login request)
{
...//some login logic
}
And also, I have my model which is:
public class Requests
{
public class Login
{
[Required(AllowEmptyStrings = false, ErrorMessage = "Email address cannot be empty!")]
[MinLength(5)]
public string Email;
[Required]
public string Password;
}
}
I'm sending both an empty request or request which Email and Password are null and still the actionContext.ModelState.IsValid evaluate it as true
Attached is an image when email was sent but password wasn't.
Following a comment, here is my request via Advanced Rest Client chrome plugin
NOTE
the image actually shows that Keys and Values are empty when in fact they are supplied..
EDIT
number of things i've also tried:
removing all other filters, why? maybe the context was messed up by another reading.
sending valid request in terms of fields, but email was 1 char long.why? maybe Requiredis working differently than others, still nothing about the min-length issue.
instead of nested objects, i created a seperate stand-alone class for the Login object. why? thought maybe the fact that it's nested the validation is not recursive.
Looping the Arguments list one-by-one and validate as object, answer is always true. never fails, why? cause Im almost admitting defeat.
instead of adding filter to config as i described in the question, tried GlobalConfiguration.Configuration.Filters.Add(new RequestValidationFilter()); instead
You need to add { get; set; } after your model properties:
public class Login
{
[Required(AllowEmptyStrings = false, ErrorMessage = "Email address cannot be empty!")]
[MinLength(5)]
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
This is necessary because the default model validation for ASP.NET only includes properties with a public get method. From PropertyHelper.cs, here's some of the code which determines whether a property on the model will be included in validation:
// Indexed properties are not useful (or valid) for grabbing properties off an object.
private static bool IsInterestingProperty(PropertyInfo property)
{
return property.GetIndexParameters().Length == 0 &&
property.GetMethod != null &&
property.GetMethod.IsPublic &&
!property.GetMethod.IsStatic;
}
This method is used to filter the properties that are used in the default model binding in MVC/Web API. Notice that it's checking whether the GetMethod exists on the property and that it's public. Since your class didn't have the get methods on its properties, they were being ignored.
If you want to know more, you can kill a lot of time looking through the ASP.NET MVC source. I think the code on github is for a newer version of ASP.NET, but it seems like a lot of the same logic applies in the version you are using.

Categories