Apr 20, 2021
This tutorial will show how you can use the Telerik Conversational UI with Weavy acting as backend in an ASP.NET MVC Project. You will also see examples on how to use some other Telerik controls throughout the project.
https://showcase.weavycloud.com
)Start by cloning the Telerik Project from Github. It contains all the code we are going to use for this tutorial. Run the project either via IIS Express or set it up as an IIS site.
https://showcase.weavycloud.com
and is fully functional. BUT, if you are interested in setting up your own local Weavy instance as the acting backend, you can also clone the Weavy solution used in this tutorial from Github. This will require some additional steps that are explained in the Configuration section.The app will showcase some essential functionality for a chat app built with the Telerik Conversational UI. It is not a production-ready application and should be regarded as an example of how to leverage Weavy functionality in a Telerik project.
These are the functions we are going to use in this tutorial:
By default, authentication is hardcoded against the public Weavy instance. There are four different user accounts you can sign in with. Authentication is done using static JWT tokens with long expiry solely for demo purposes.
Clicking sign-in will set a local cookie using regular forms authentication. After signing in, the menu will now display the Chat menu item.
Navigate to /chat
to view the application. There is a lot going on here, we'll break it down for you.
The Telerik ListView component is created using a Telerik data source, that gets its data from Weavy via the Weavy REST API.
// load conversations
_conversations = new kendo.data.DataSource({
transport: {
read: function (options) {
_weavy.ajax("/api/conversations").then(function (conversations) {
if (conversations.length === 0) {
$("#no-conversations").removeClass("d-none");
} else {
$("#no-conversations").addClass("d-none");
conversations.forEach(function (c) {
c.thumb = _weavy.options.url + c.thumb.replace("{options}", "96");
if (c.isRoom) {
c.title = c.name;
} else {
if (c.members.length > 1) {
var u = c.members.filter(x => x.id !== _weavy.user.id)[0];
c.title = typeof (u.name) === "undefined" ? u.username : u.name;
} else {
c.title = c.members[0].name === "undefined" ? c.members[0].username : c.members[0].name;
}
}
});
options.success(conversations);
// open first conversation
if (_activeConversation == null) {
loadConversation(conversations[0].id);
}
}
});
}
}
});
_listView = $("#listView").kendoListView({
dataSource: _conversations,
selectable: "single",
template: kendo.template($("#template").html())
}).data("kendoListView");
_listView.bind("change", onChangeConversation);
By using the Weavy Client SDK and its built-in ajax() method, we don't need to worry about authentication (and other things), which is done under the hood and allows us to easily make requests and handle the response from the API.
For instance: The snippet below makes a GET
request to the Weavy instance we have configured, returning the existing conversations for us to use when populating the DataSource.
_weavy.ajax("/api/conversations").then(function (conversations) {
// work with the response...
});
No conversations to display? Then we need to add some.
Click the New Conversation button and select the members of the conversation. The Telerik MultiSelect component is fed its result from Weavy. After you click to create the API is called and the conversation is created. The UI is then updated to reflect the new data.
// select users for new conversation
$("#users").kendoMultiSelect({
placeholder: "Select conversation members...",
dataTextField: "profile.name",
dataValueField: "id",
autoBind: false,
dataSource: {
serverFiltering: true,
transport: {
read: function (options) {
_weavy.ajax("/api/users").then(function (users) {
users.data.forEach(function (u) {
if (typeof (u.profile.name) === "undefined") {
u.profile.name = u.username;
}
});
options.success(users.data);
});
}
}
}
});
...
// create new conversation
$(document).on("click", "#create-conversation", function () {
if (_users.value().length > 0) {
_weavy.ajax("/api/conversations", {
members: _users.value()
}, "POST").then(function (response) {
$("#listView").data("kendoListView").dataSource.read();
loadConversation(response.id);
_users.value([]);
$("#new-conversation").modal("hide");
});
}
});
The data source part is bound to the /api/users
endpoint, which returns existing Weavy users. Again, using the built-in Weavy.ajax()
methods make it easy to work with.
When the Create button is clicked, the conversation is created and the UI is updated (list of conversations refreshed, chat loaded, and the multi-select reset, etc.)
The Telerik Chat component is populated when a user clicks a conversation. First, the conversation is fetched and then all the messages in the conversation are retrieved and rendered using the renderMessage
function.
// load messages in chat
function loadChat(id) {
_weavy.ajax("/api/conversations/" + id).then(function (conversation) {
_activeConversation = conversation.id;
_weavy.ajax("/api/conversations/" + id + "/messages").then(function (messages) {
if (_chat !== null) {
$("#chat").data("kendoChat").destroy();
$("#chat").empty();
}
$("#chat").kendoChat({
user: {
iconUrl: _weavy.options.url + _weavy.user.thumb.replace("{options}", "96"),
name: _weavy.user.name
}, sendMessage: function (e) {
_weavy.ajax("api/conversations/" + id + "/messages", {
text: e.text
}, "POST").then(function (message) {
});
}, typingStart: function (e) {
_weavy.ajax("api/conversations/" + id + "/typing", null, "POST");
}, typingEnd: function (e) {
_weavy.ajax("api/conversations/" + id + "/typing", null, "DELETE");
}
});
_chat = $("#chat").data("kendoChat");
if (typeof (messages.data) !== "undefined") {
messages.data.forEach(function (m) {
renderMessage(m);
});
}
});
});
}
// renders a message in the active chat
function renderMessage(message) {
var user = message.createdBy.id == _weavy.user.id ? _chat.getUser() : {
id: message.createdBy.id,
name: typeof (message.createdBy.name) === "undefined" ? message.createdBy.username : message.createdBy.name,
iconUrl: _weavy.options.url + message.thumb.replace("{options}", "96"),
};
_chat.renderMessage({
type: "text",
text: message.text,
timestamp: new Date(message.createdAt)
}, user);
}
The typingStart
and typingEnd
events are hooked up so that a call to the API is done when typing status changes. These events are then distributed in real-time to other users so that they will see when a member of the conversation is typing.
In order to take advantage of Weavy real-time functionality, we are instantiating a Weavy Client. Via the client, we can respond to various events being distributed in real-time. You can find a list of available events in the Server API / Weavy.Core.Events section.
_weavy = new Weavy({ jwt: getToken });
// wait for loaded event
_weavy.whenLoaded().then(function () {
// handle realtime events
_weavy.connection.on("message-inserted.weavy", function (e, message) {
if (message.conversation === _activeConversation && _weavy.user.id !== message.createdBy.id) {
renderMessage(message);
} else {
$("#listView").data("kendoListView").dataSource.read();
}
});
_weavy.connection.on("badge.weavy", function (e, item) {
var badge = _badge.find(".k-badge")
if (item.conversations > 0) {
badge.removeClass("k-hidden");
} else {
badge.addClass("k-hidden");
}
badge.text(item.conversations);
});
_weavy.connection.on("typing.weavy", function (e, item) {
if (item.conversation === _activeConversation) {
_chat.renderUserTypingIndicator({ name: item.name, id: item.user.id });
}
});
_weavy.connection.on("typing-stopped.weavy", function (e, item) {
if (item.conversation === _activeConversation) {
_chat.clearUserTypingIndicator({ name: item.name, id: item.user.id });
}
});
// code removed for readability...
});
The real-time events we are interested in are the message-inserted
event - for updating the conversations list, the badge
event - for updating our badge control when the number of unread conversations changes, the typing
and typing-stopped
event - for showing/hiding the typing indicator in the Telerik Chat component.
The typing-stopped
event is actually a custom event that we trigger via the API. More on that later.
Let's move away from the Telerik MVC project for a while and focus on the Weavy installation that backs the UI we have been working with. You can disregard this section if you are running the default project at https://showcase.weavycloud.com.
Start by cloning the Weavy Showcase project. Build the project and deploy locally or to a location of your choosing. Refer to the Getting Started Guide for help on setting up Weavy.
Complete the setup wizard, then navigate to /manage/clients
. Add a new client and copy the values for client id
and client secret
. Also note, the URL Weavy is configured to run on.
Go back to the Telerik MVC project. Locate the file ~/web.config
, open it and enter the URL
, client id
and client secret
in the appSettings section. See below:
<!-- Update weavy-url if you are using your own Weavy instance -->
<add key="weavy-url" value="https://showcase.weavycloud.com" />
<!-- Update these values to match with the Application Client in your Weavy instance -->
<add key="weavy-client-id" value="id" />
<add key="weavy-client-secret" value="secret" />
And lastly, in the Telerik MVC project, locate the file ~/Views/Account/SignIn.cshtml
. There are two login forms on the page. Make sure that the first form is active. The second form is used when authenticating against the publicly available Weavy instance. Now you are using your own Weavy Server SDK.
The logic for authenticating users is really simple and un-secure for demo purposes. If you are interested in building something else, take a look in the ~/Controllers/AccountController.cs
file.
Weavy is highly extendable, enabling developers the ability to modify most parts of the product. For this project, we have been using REST API endpoints that we created custom for this project. Being able to modify and extend the Weavy REST API can be a powerful tool for building applications that are efficient and complex. Below you can see the endpoints used in this tutorial.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Http;
using System.Web.Http.Description;
using Weavy.Areas.Api.Models;
using Weavy.Core;
using Weavy.Core.Models;
using Weavy.Core.Services;
using Weavy.Web.Api.Controllers;
using Weavy.Web.Api.Models;
namespace Weavy.Areas.Api.Controllers {
/// <summary>
/// Api controller for manipulating Conversations.
/// </summary>
[RoutePrefix("api")]
public class ConversationsController : WeavyApiController {
/// <summary>
/// Get the <see cref="Conversation" /> with the specified id.
/// </summary>
/// <param name="id">The conversation id.</param>
/// <example>GET /api/conversations/527</example>
/// <returns>The specified conversation.</returns>
[HttpGet]
[ResponseType(typeof(Conversation))]
[Route("conversations/{id:int}")]
public IHttpActionResult Get(int id) {
// read conversation
ConversationService.SetRead(id, DateTime.UtcNow);
var conversation = ConversationService.Get(id);
if (conversation == null) {
ThrowResponseException(HttpStatusCode.NotFound, $"Conversation with id {id} not found.");
}
return Ok(conversation);
}
/// <summary>
/// Get all <see cref="Conversation" /> for the current user.
/// </summary>
/// <example>GET /api/conversations</example>
/// <returns>The users conversations.</returns>
[HttpGet]
[ResponseType(typeof(IEnumerable<Conversation>))]
[Route("conversations")]
public IHttpActionResult List() {
var conversations = ConversationService.Search(new ConversationQuery());
return Ok(conversations);
}
/// <summary>
/// Create a new or get the existing conversation between the current- and specified user.
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost]
[ResponseType(typeof(Conversation))]
[Route("conversations")]
public IHttpActionResult Create(CreateConversationIn model) {
string name = null;
if (model.Members.Count() > 1) {
name = string.Join(", ", model.Members.Select(u => UserService.Get(u).GetTitle()));
}
// create new room or one-on-one conversation or get the existing one
return Ok(ConversationService.Insert(new Conversation() { Name = name }, model.Members));
}
/// <summary>
/// Get the messages in the specified conversation.
/// </summary>
/// <param name="id">The conversation id.</param>
/// <param name="opts">Query options for paging, sorting etc.</param>
/// <returns>Returns a conversation.</returns>
[HttpGet]
[ResponseType(typeof(ScrollableList<Message>))]
[Route("conversations/{id:int}/messages")]
public IHttpActionResult GetMessages(int id, QueryOptions opts) {
var conversation = ConversationService.Get(id);
if (conversation == null) {
ThrowResponseException(HttpStatusCode.NotFound, "Conversation with id " + id + " not found");
}
var messages = ConversationService.GetMessages(id, opts);
messages.Reverse();
return Ok(new ScrollableList<Message>(messages, Request.RequestUri));
}
/// <summary>
/// Creates a new message in the specified conversation.
/// </summary>
/// <param name="id"></param>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost]
[ResponseType(typeof(Message))]
[Route("conversations/{id:int}/messages")]
public IHttpActionResult InsertMessage(int id, InsertMessageIn model) {
var conversation = ConversationService.Get(id);
if (conversation == null) {
ThrowResponseException(HttpStatusCode.NotFound, "Conversation with id " + id + " not found");
}
return Ok(MessageService.Insert(new Message { Text = model.Text, }, conversation));
}
/// <summary>
/// Called by current user to indicate that they are typing in a conversation.
/// </summary>
/// <param name="id">Id of conversation.</param>
/// <returns></returns>
[HttpPost]
[Route("conversations/{id:int}/typing")]
public IHttpActionResult StartTyping(int id) {
var conversation = ConversationService.Get(id);
// push typing event to other conversation members
PushService.PushToUsers(PushService.EVENT_TYPING, new { Conversation = id, User = WeavyContext.Current.User, Name = WeavyContext.Current.User.Profile.Name ?? WeavyContext.Current.User.Username }, conversation.MemberIds.Where(x => x != WeavyContext.Current.User.Id));
return Ok(conversation);
}
/// <summary>
/// Called by current user to indicate that they are no longer typing.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpDelete]
[Route("conversations/{id:int}/typing")]
public IHttpActionResult StopTyping(int id) {
var conversation = ConversationService.Get(id);
// push typing event to other conversation members
PushService.PushToUsers("typing-stopped.weavy", new { Conversation = id, User = WeavyContext.Current.User, Name = WeavyContext.Current.User.Profile.Name ?? WeavyContext.Current.User.Username }, conversation.MemberIds.Where(x => x != WeavyContext.Current.User.Id));
return Ok(conversation);
}
/// <summary>
/// Marks a conversation as read for the current user.
/// </summary>
/// <param name="id">Id of the conversation to mark as read.</param>
/// <returns>The read conversation.</returns>
[HttpPost]
[Route("conversations/{id:int}/read")]
public Conversation Read(int id) {
ConversationService.SetRead(id, readAt: DateTime.UtcNow);
return ConversationService.Get(id);
}
/// <summary>
/// Get the number of unread conversations.
/// </summary>
/// <returns></returns>
[HttpGet]
[ResponseType(typeof(int))]
[Route("conversations/unread")]
public IHttpActionResult GetUnread() {
return Ok(ConversationService.GetUnread().Count());
}
}
}
In the StopTyping
method you can see how easy it is to raise custom events. These events can easily be hooked up to using the Weavy Client SDK, as described in the The Weavy Client SDK and Realtime Events section.
This tutorial has been trying to demonstrate how you can use the Telerik UI framework to work with data that is being served from and persisted in Weavy. Weavy is designed around well-known standards and provides great flexibility when it comes to building your own stuff.
Select what technology you want to ask about
Get answers faster with our tailored generative AI. Learn more about how it works
This is a custom LLM for answering your questions. Answers are based on the contents of the documentation and this feature is experimental.
To access live chat with our developer success team you need a Weavy account.