Why is ASP.NET not deserializing my post method parameter? - c#

I am trying to learn the basics of ASP.NET. I decided to try to make a telegram bot and my first step is to get it to loop back my messages to it. I am using Telegram.Bot as my client. I set up the project using VS Code connected remotely to a Ubuntu 20.04 machine next to my windows PC (which I am working from). dotnet --version says I am running 3.1.4 (on the linux machine). I ran dotnet new mvc -o telegram-odin. Then I added the BotController class seen below. I have used this class to test various endpoints and methods. Just feeling out how to do things. I have my telegram bot token stored in an environment variable on the linux machine. I am using ngrok to allow telegram's webhooks to post back to me.
My issue is that I can't seem to get any post endpoints to correctly accept objects. I tried using Telegram.Bot's webhook example, but this just doesn't work. Update is always null, and the Request.Body is always empty. I have searched around and found other people with issues of null objects when using the [FromBody] attribute, but they all talked about receiving primitive types instead of objects. I did see that someone mentioned that something called "Middleware" might be consuming the body when I use [FromBody]. So I removed it and tried to parse the Request.Body directly. I found that I would get a default (the same as the result of new Update()) value for update, but more importantly the Request.Body has the JSON data that I can grab and parse. Theoretically I am in business now and can just deserialize it. However, I feel like I should be able to let ASP.NET just handle that for me. So something must be wrong.
My configured services.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddNewtonsoftJson();
services.AddControllersWithViews();
services.AddSingleton<TelegramBotClient>(new TelegramBotClient(Program.GetBotToken()));
}
My Controller class.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Telegram.Bot;
using Telegram.Bot.Types;
using Newtonsoft.Json;
using System.IO;
namespace telegram_odin.Controllers
{
public class BotController : ControllerBase
{
private readonly ILogger<BotController> _logger;
private readonly TelegramBotClient _telegramClient;
public BotController(ILogger<BotController> logger, TelegramBotClient telegramClient)
{
_logger = logger;
_telegramClient = telegramClient;
// I know I am resetting the endpoint every time one of these endpoints is called.
// I will fix this later.
var botClient = _telegramClient;
botClient.SetWebhookAsync(GetWebhookInEndpoint());
}
public async Task<string> Index()
{
var botClient = _telegramClient;
var me = await botClient.GetMeAsync();
return me.ToString();
}
public async Task<string> GetWebhookInfo()
{
var botClient = _telegramClient;
var webhookInfo = await botClient.GetWebhookInfoAsync();
return JsonConvert.SerializeObject(webhookInfo).ToString();
}
[HttpPost]
public async Task<IActionResult> Update(Update update)
{
try
{
if (update is null)
{
_logger.LogInformation("update is null");
}
else
{
string updateAsJson = JsonConvert.SerializeObject(update);
_logger.LogInformation($"update as json - {updateAsJson}");
}
string body = "";
using (StreamReader reader =new StreamReader(Request.Body))
{
body = await reader.ReadToEndAsync();
}
_logger.LogInformation($"update body - {body}");
return Ok();
}
catch (Exception ex)
{
return Problem(ex.Message);
}
}
// I know this is the wrong place for this method.
// Quick and dirty is the way to learn.
private string GetWebhookInEndpoint()
{
// I wrote a helper to pull the https Uri by asking my local ngrok server about its' open tunnels.
var ngrokUri = NgrokHelpers.GetNgrokUri();
var wholeUri = new Uri(ngrokUri, "Bot/Update");
_logger.LogInformation($"generated webhook endpoint - {wholeUri.ToString()}");
return wholeUri.ToString();
}
}
}
This is Telegram.Bot's Update definition.
#region Assembly Telegram.Bot, Version=15.7.1.0, Culture=neutral, PublicKeyToken=null
// Telegram.Bot.dll
#endregion
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.Payments;
namespace Telegram.Bot.Types
{
//
// Summary:
// This object represents an incoming update.
//
// Remarks:
// Only one of the optional parameters can be present in any given update.
[JsonObject(MemberSerialization.OptIn, NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
public class Update
{
public Update();
//
// Summary:
// The update's unique identifier. Update identifiers start from a certain positive
// number and increase sequentially. This ID becomes especially handy if you're
// using Webhooks, since it allows you to ignore repeated updates or to restore
// the correct update sequence, should they get out of order.
[JsonProperty("update_id", Required = Required.Always)]
public int Id { get; set; }
//
// Summary:
// Optional. New incoming message of any kind — text, photo, sticker, etc.
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public Message Message { get; set; }
//
// Summary:
// Optional. New version of a message that is known to the bot and was edited
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public Message EditedMessage { get; set; }
//
// Summary:
// Optional. New incoming inline query
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public InlineQuery InlineQuery { get; set; }
//
// Summary:
// Optional. The result of a inline query that was chosen by a user and sent to
// their chat partner
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public ChosenInlineResult ChosenInlineResult { get; set; }
//
// Summary:
// Optional. New incoming callback query
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public CallbackQuery CallbackQuery { get; set; }
//
// Summary:
// Optional. New incoming channel post of any kind — text, photo, sticker, etc.
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public Message ChannelPost { get; set; }
//
// Summary:
// Optional. New version of a channel post that is known to the bot and was edited
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public Message EditedChannelPost { get; set; }
//
// Summary:
// Optional. New incoming shipping query. Only for invoices with flexible price
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public ShippingQuery ShippingQuery { get; set; }
//
// Summary:
// Optional. New incoming pre-checkout query. Contains full information about checkout
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public PreCheckoutQuery PreCheckoutQuery { get; set; }
//
// Summary:
// New poll state. Bots receive only updates about polls, which are sent or stopped
// by the bot
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public Poll Poll { get; set; }
//
// Summary:
// Optional. A user changed their answer in a non-anonymous poll. Bots receive new
// votes only in polls that were sent by the bot itself.
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public PollAnswer PollAnswer { get; set; }
//
// Summary:
// Gets the update type.
//
// Value:
// The update type.
public UpdateType Type { get; }
}
}

Related

How does .NET Core C# Minimal API fill parameters?

With minimalistic API how does MapGet automatically fill parameters from querystring?
With minimalistic API the following is possible:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("api/Students/Grades", StudentsDataContext.GetGradesAsync).RequireAuthorization("Admin");
//...
public class Grade
{
public string? Subject { get; set; }
public int GradePercentage { get; set; } = 0;
}
public class StudentsDataContext
{
public static async Task<List<Grade>> GetGradesAsync(int? studentId, ClaimsPrincipal user, CancellationToken ct))
{
// Gets grades from database...
return new List<Grade>() {
new () { Subject = "Algebra", GradePercentage=95 },
new () { Subject = "English", GradePercentage=90 }
};
}
}
When you call: /api/Students/Grades?studentId=5
magically, studentId is passed to the GetGradesAsync, as well as ClaimsPrinicipal, and CancellationToken.
How does this witchcraft work? Is it possible to learn this power of the darkside?
The link you have provided describes the rules of the parameter binding in Minimal APIs. In the nutshell it is pretty simple - the request handler delegate is analyzed (via some reflection+runtime code generation or source generation at build time I suppose) and actual handler is created were all parameters are processed accordingly and passed to user defined one.
I have not spend much time so the following can be not entirely correct, but the starting point of the investigation how does it actually work would be EndpointRouteBuilderExtensions which leads to RequestDelegateFactory (see AddRouteHandler call which fills List<RouteEntry> _routeEntries which is later processed with CreateRouteEndpointBuilder method, which calls CreateHandlerRequestDelegate) which should contain all the actual "magic".

MassTransit RabbitMq message is not being consumed. C#

I have library API in ASP NET Core 3.1 MVC where users can borrow, return and follow borrowed book status. I want to create email notification so when book is returned, all the users that are following this specific book status will recieve email notification that its available.
I want to use RabbitMQ with MassTransit and handle the emails on different web service.
This is my code that is sending messages to the rabbit queue:
public async Task SendNotificationStatus(Book book, CancellationToken cancellationToken)
{
var endpoint = await _bus.GetSendEndpoint(new System.Uri($"rabbitmq://{_rabbitHostName}/library-notifications"));
var bookSpectators = await _userRepository.GetSpectatorsByBookId(book.Id, cancellationToken);
foreach (var user in bookSpectators)
{
NotifyStatusReturn rabbitMessage = new NotifyStatusReturn
{
NotificationType = NotificationTypes.BookReturn,
RecipientAddress = user.EmailAddress,
RecipientLogin = user.Login,
SentDate = DateTime.UtcNow,
BookTitle = book.Title
};
await endpoint.Send(rabbitMessage);
}
}
And when it comes to the notifications service i've created the project with templates provided on MassTransit website - https://masstransit-project.com/usage/templates.html#installation
First I ran dotnet new mtworker -n LibraryNotifications and then after going inside the project folder dotnet new mtconsumer
I've added MassTransit.RabbitMq 8.0.0 package via NugerPackage Manager.
In Contracts folder created via mtconsumer template i ve changed the name of the record to NotifyStatusReturn which look like this:
namespace Contracts
{
public record NotifyStatusReturn
{
public string NotificationType { get; set; }
public string RecipientAddress { get; set; }
public string RecipientLogin { get; set; }
public DateTime SentDate { get; set; }
public string BookTitle { get; set; }
}
}
And in Program.cs swapped the x.UsingInMemory() to
x.UsingRabbitMq((context, cfg) =>
{
cfg.Host("localhost", "/", h =>
{
h.Username("guest");
h.Password("guest");
});
cfg.ConfigureEndpoints(context);
});
When i return the book, the message goes into library-notifications_skipped queue as a dead-letter. All the bindings seems okay for me and i really dont know what is the reason that my messages are not being consumed. Could anybody help me with this issue?
As per the documentation:
MassTransit uses the full type name, including the namespace, for message contracts. When creating the same message type in two separate projects, the namespaces must match or the message will not be consumed.
Make sure that your message type has the same namespace/type in each project.

FormFlow prompts are not being spoken in Cortana Skills

I am building a Coratana skill by first building a bot, using FormFlow. I detect my intents and entities using LUIS and pass the entities to my FormFlow dialog. If one or more FormFlow fields is not filled in, FormFlow dialog prompts the user to fill in the missing information, but this prompt is not spoken, only shows on the cortana screen. Is there any way for FormFlow to speak the prompts?
In the screenshot shown below, the prompt "Do you need airport shuttle?" was just displayed and not spoken:
My formFlow definition looks like this:
[Serializable]
public class HotelsQuery
{
[Prompt("Please enter your {&}")]
[Optional]
public string Destination { get; set; }
[Prompt("Near which Airport")]
[Optional]
public string AirportCode { get; set; }
[Prompt("Do you need airport shuttle?")]
public string DoYouNeedAirportShutle { get; set; }
}
I don't think Speak is currently supported in FormFlow.
What you could do, as a workaround is adding an IMessageActivityMapper that basically promote text to speak automatically.
namespace Code
{
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Internals;
using Microsoft.Bot.Connector;
/// <summary>
/// Activity mapper that automatically populates activity.speak for speech enabled channels.
/// </summary>
public sealed class TextToSpeakActivityMapper : IMessageActivityMapper
{
public IMessageActivity Map(IMessageActivity message)
{
// only set the speak if it is not set by the developer.
var channelCapability = new ChannelCapability(Address.FromActivity(message));
if (channelCapability.SupportsSpeak() && string.IsNullOrEmpty(message.Speak))
{
message.Speak = message.Text;
}
return message;
}
}
}
Then to use it, you need to register it in your Global.asax.cs file as:
var builder = new ContainerBuilder();
builder
.RegisterType<TextToSpeakActivityMapper>()
.AsImplementedInterfaces()
.SingleInstance();
builder.Update(Conversation.Container);
The answer form Ezequiel Jadib helped me to solve what I needed for my use case. I just added a few additional lines to set the InputHint field to ExpectingInput if the text is a question. With this configuration Cortana automatically listens to my answer and I don't have to activate the microphone by myself.
public IMessageActivity Map(IMessageActivity message)
{
// only set the speak if it is not set by the developer.
var channelCapability = new ChannelCapability(Address.FromActivity(message));
if (channelCapability.SupportsSpeak() && string.IsNullOrEmpty(message.Speak))
{
message.Speak = message.Text;
// set InputHint to ExpectingInput if text is a question
var isQuestion = message.Text?.EndsWith("?");
if (isQuestion.GetValueOrDefault())
{
message.InputHint = InputHints.ExpectingInput;
}
}
return message;
}

Azure Table Controllers - Get records by to parameters

I am working on an Azure Mobile Apps project. Where I have to define a Table Controller with that can accept two parameters and give a list of values. I have a DataObject for ProductItem, which is
public class ProductItem : EntityData
{
public string Name { get; set; }
public string Details { get; set; }
public double Price { get; set; }
public string Image { get; set; }
public Merchant Merchant { get; set; }
}
I need to get a specific Product item, filter by its Price and Merchant. Already in the ProductItemContoller, I have scaffolded
// GET tables/ProductItem
public IQueryable<ProductItem> GetAllProductItems()
{
return Query();
}
// GET tables/ProductItem/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<ProductItem> GetProductItem(string id)
{
return Lookup(id);
}
by looking at existing examples. But in examples, we have not called any of the given methods from Client. Rather, IEnumerable<ProductItem> items = await productTable.ToEnumerableAsync(); was called.
My question is why can't we call GetAllProductItems() which was already defined in the controller to the client. If we can call, how to do it.
And also, I need to have a controller method, I need to have a GetAllProductByMerchat(string merchantId). How can I make this possible.
The Table controllers are called automatically by the client SDKs on your behalf, allowing you to work with LINQ queries on the client. You can use something like:
var items = productTable.Where(p => p.Price < 100).ToListAsync();
This gets translated into an OData query across the wire, then translated back into a LINQ query on the server, where it then gets translated into SQL and executed on the SQL Azure instance.
For more information, see chapter 3 of http://aka.ms/zumobook
Did you mean this?
// Server method:
[HttpGet]
[Route("GetAllProductItems")]
public IQueryable<ProductItem> GetAllProductItems()
{
return Query();
}
// Client call
var result = await MobileService.InvokeApiAsync<IQueryable<ProductItem>>("ProductItem/GetAllProductItems", HttpMethod.Get, null);
Remember to add these attribute before the ProductItemController:
[MobileAppController]
[RoutePrefix("api/ProductItem")]
You can do the same thing to your GetAllProductByMerchat(string merchantId) method.

Hypermedia links with Servicestack new API

I am evaluating how to add hypermedia links to DTO responses. Although there is no standard, add List to the response DTOs seems to be the suggested approach.
Do you know of any example or reference of implementation using ServiceStack framework?
Adding List is ok for me, but my doubts are about where to put the logic of the following links (Within the service or a specialized class that holds the state machine?) and where to resolve the routes (A filter?)
Thanks.
[Update] From ServiceStack version v3.9.62 it is posible to access Routes configuration via EndpointHost.Config.Metadata.Routes.RestPath, so the solution provided by tgmdbm can be improved withouth the need of "IReturn + Routes attributes", just using Metadata.Routes information.
In fact all service metadata can be queried and used to cross-cutting concerns. Servicestack rocks.
The way I do this currently is I pass back a response dto which implements an interface
public interface IHaveLinks
{
[IgnoreDataMember]
IEnumerable<Link> Links { get; }
}
public class Link
{
public string Name { get; set; }
public IReturn Request { get; set; }
public string Method { get; set; }
}
Then I use a response filter to generate the urls and populate the response headers with the links.
this.ResponseFilters.Add((req, res, dto) =>
{
if (!(dto is IHaveLinks))
return;
var links = (dto as IHaveLinks).Links
if(links == null || !links.Any())
return;
var linksText = links
.Select(x => string.Format("<{0}>; rel={1}"), x.Request.ToUrl(x.Method), x.Name));
var linkHeader = string.Join(", ", linksText);
res.AddHeader("Link", linkHeader);
});
This seems the cleanest way. The Link object above effectively says "If you make this request with this method you will get back the named resource". The only HTTP thing that bleeds up to the BLL is Method. But you could get rid of that and only pass back GET urls. Or map it to some generalised "operation"?
As an example:
public class ExampleService : Service
{
public ExamplesResponse Get(ExamplesRequest request)
{
var page = request.Page;
var data = // get data;
return new ExamplesResponse
{
Examples = data,
Links = new []
{
new Link { Name = "next", Request = request.AddPage(1), Method = "GET" },
new Link { Name = "previous", Request = request.AddPage(-1), Method = "GET" },
}
}
}
}
[Route("/examples/{Page}")]
public class ExamplesRequest : IReturn<ExamplesResponse>
{
public int Page { get; set; }
// ...
}
(The AddPage method returns a clone of the request and sets the Page property appropriately.)
Hope that helps.

Categories

Resources