Signalr & WebSocketSharp in Unity3d - unity3d

I've currently built a simple Signalr Hub which I'm pushing messages to from a Unity5 project. Given that SignalR2 client doesn't work with Unity5 I'm using websocketsharp in order to intercept the websocket frames. The messages are being pushed to the Hub successfully, but when I attempt to call a method on the client, I do not get the payload string, only the message identifier {"I": 0}
Looking through the SignalR documentation, it looks like this gets sent last, but I have no idea how I can get a hold it it. I'm sure its something simple, but for the life of me I can't figure it out.
UPDATE
Upon request, I've added the code for the project below...
SignalRClient.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Newtonsoft.Json;
using WebSocketSharp;
namespace Assets.Scripts
{
class SignalRClient
{
private WebSocket _ws;
private string _connectionToken;
private Dictionary<string, UnTypedActionContainer> _actionMap;
private readonly string _socketUrl = "http://localhost/";
private readonly string _socket = "ws://localhost/";
public SignalRClient()
{
_actionMap = new Dictionary<string, UnTypedActionContainer>();
var webRequest = (HttpWebRequest)WebRequest.Create(_socketUrl + "/signalr/negotiate?connectionData=%5B%7B%22name%22%3A%22myHub%22%7D%5D&clientProtocol=1.3");
var response = (HttpWebResponse)webRequest.GetResponse();
using (var sr = new StreamReader(response.GetResponseStream()))
{
var payload = sr.ReadToEnd();
UnityEngine.Debug.Log(payload);
_connectionToken = Uri.EscapeDataString(JsonConvert.DeserializeObject<NegotiateResponse>(payload).ConnectionToken);
//UnityEngine.Debug.Log(_connectionToken);
}
}
public void Open()
{
_ws = _ws == null
? new WebSocket(_socket + "signalr/connect?transport=webSockets&connectionToken=" + _connectionToken)
: new WebSocket(_socket + "signalr/reconnect?transport=webSockets&connectionToken=" + _connectionToken);
AttachAndConnect();
}
public void Close()
{
_ws.Close();
}
public void SendMessage(string name, string message)
{
//{"H":"chathub","M":"Send","A":["tester","hello"],"I":0}
var payload = new RollerBallWrapper()
{
H = "myhub",
M = "Send",
A = new[] { name, message },
I = 12
};
var wsPacket = JsonConvert.SerializeObject(payload);
_ws.Send(wsPacket);
}
private void AttachAndConnect()
{
_ws.OnClose += _ws_OnClose;
_ws.OnError += _ws_OnError;
_ws.OnMessage += _ws_OnMessage;
_ws.OnOpen += _ws_OnOpen;
_ws.Connect();
}
void _ws_OnOpen(object sender, EventArgs e)
{
UnityEngine.Debug.Log("Opened Connection");
}
//
// This seems to be retriving the last frame containing the Identifier
void _ws_OnMessage(object sender, MessageEventArgs e)
{
//UnityEngine.Debug.Log(e.Data); // Returns {"I":"0"} ????
}
void _ws_OnError(object sender, WebSocketSharp.ErrorEventArgs e)
{
UnityEngine.Debug.Log(e.Message);
}
void _ws_OnClose(object sender, CloseEventArgs e)
{
UnityEngine.Debug.Log(e.Reason + " Code: " + e.Code + " WasClean: " + e.WasClean);
}
public void On<T>(string method, Action<T> callback) where T : class
{
_actionMap.Add(method, new UnTypedActionContainer
{
Action = new Action<object>(x =>
{
callback(x as T);
}),
ActionType = typeof(T)
});
}
}
internal class UnTypedActionContainer
{
public Action<object> Action { get; set; }
public Type ActionType { get; set; }
}
class MessageWrapper
{
public string C { get; set; }
public RollerBallWrapper[] M { get; set; }
}
class RollerBallWrapper
{
public string H { get; set; }
public string M { get; set; }
public string[] A { get; set; }
public int I { get; set; }
}
}
MyHub.cs
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
public class MyHub : Hub
{
public void Send(string name, string message)
{
var myConn = Context.ConnectionId;
Clients.All.broadcastMessage("John", "Hello");
}
}

The problem is the websocket connection. I had the following:
new WebSocket(_socket + "signalr/connect?transport=webSockets&connectionToken=" + _connectionToken)
Which was missing 2 critical querystring parameters: connectionData and tid in addition to the connectionToken and transport. I wrongly assumed that these weren't needed.
I hope this helps anyone who didn't read the documentation like me :)

Related

How do you isolate a text string from in a chatbox/text field in Unity?

I am making a chatbot (well I'm attempting!) and I have set up wit.ai to handle the text-to-speech of the bot. My issue is that I am pulling all of the chat into the speaker on each update which is overloading the TTS, when I really only want the line of the Bot (Dave) to go through the speaker and update with each new line. How can I isolate only the bots lines? Would love some help with this!
public class SimpleCharacter : MonoBehaviour
{
public GameObject item;
public Scrollbar verticalScrollbar;
public ScrollRect scrollRect;
public TMP_Text chatText;
private Animator anim;
public TTSSpeaker _speaker;
// Start is called before the first frame update
void Start()
{
Debug.Log("Simple Character start 0 ");
anim = GetComponentInChildren<Animator>();
if (anim == null)
{
Debug.Log("Simple Character anim is null");
}
Debug.Log("Simple Character start 1");
}
public void Think (string text)
{
string chat = chatText.text;
chat = chat + "/n" + text;
chatText.text = text;
anim.SetTrigger("Think");
}
public void Talk(List<Choice> choices)
{
string chat = chatText.text;
chatText.text = "";
Debug.Log("////////////////////// : " + chat);
chat = chat + "/n" + choices[0].ToString();
Debug.Log("////////////////////// : " + chat);
chatText.text = chat;
chatText.text = choices[0].ToString();
anim.SetTrigger("Talk");
}
public void Talk (string text)
{
string chat = chatText.text;
chat = chat + "/n" + text;
chatText.text = chat;
chatText.text = text;
_speaker.Speak(chatText.text);
}
}
Daves's lines are being received from this character's script
namespace OpenAI_Unity
{
public class OAICharacter : OAICompletion
{
protected override StringBuilder GetDefaultInformation()
{
Debug.Log("GetDefaultInformation - OAICharacter");
var sb = base.GetDefaultInformation();
sb.Replace("[Subject]", this.characterName);
sb.Append($"\n\nHuman: Hi\n{characterName}: Hello\nHuman: ");
return sb;
}
public override string Description { get => $"The following is a conversation between a Human and {characterName}.\n"; set => throw new System.NotImplementedException(); }
public override string InjectStartText { get => "\n" + characterName + ":"; set => throw new System.NotImplementedException(); }
[SerializeField]
private string characterName = "Dave";
public override string InjectRestartText { get => "\nHuman: "; set => throw new System.NotImplementedException(); }
public override string[] StopSequences { get => new string[] { "\n", "Human:" }; set => throw new System.NotImplementedException(); }
public override int NumOutputs { get => 1; set => throw new NotImplementedException(); }
private void ThrowError (string value)
{
Debug.LogError($"Can not set OAICharacter variable to {value}! If you want to modify these please use an OAISimpleObject instead");
}
}
}
This is the controller script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using OpenAI_Unity;
using TMPro;
public class ChatController : MonoBehaviour
{
public OAICharacter _oaiCharacter;
public TMP_InputField questionInput;
// Start is called before the first frame update
void Start()
{
questionInput.onEndEdit.AddListener((string data) =>
{
if (!string.IsNullOrEmpty(data))
{
_oaiCharacter.AddToStory(data);
}
questionInput.text = "";
});
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Tab))
{
questionInput.Select();
questionInput.ActivateInputField();
}
}
}
And the completion
namespace OpenAI_Unity
{
/// <summary>
/// Used for objects that communicate with OpenAI Completion
/// Abstract itself, for a Generic and fully customizable Completion object use OAIGenericCompletion
/// </summary>
public abstract class OAICompletion : MonoBehaviour
{
public abstract string Description
{
get; set;
}
public abstract string InjectStartText
{
get; set;
}
public abstract string InjectRestartText
{
get; set;
}
public abstract string[] StopSequences
{
get; set;
}
public EngineEnum engine;
public enum EngineEnum
{
Ada,
Babbage,
Curie,
Davinci
}
public enum LogLevel
{
None,
Responses,
ResponsesAndMemory
}
public LogLevel logLevel;
public int Max_tokens = 16;
[Range(0, 1)]
public double Temperature = 0.1;
[Range(0, 1)]
public double Top_p = 1;
public abstract int NumOutputs {get;set;}
[Range(0, 1)]
public double PresencePenalty = 1;
[Range(0, 1)]
public double FrequencyPenalty = 1;
public int LogProbs = 1;
public StringEvent QuestionReceivedEvent;
public ChoicesEvent ResponseReceivedEvent;
public ChoiceEvent ResponseReceivedEvent1;
/// <summary>
/// This can be disabled when using multiple responses, since they should not be manually added to the entire memory
/// </summary>
[HideInInspector]
public bool autoAddResponseToMemory = true;
public StringBuilder memory ;
private void Start()
{
Debug.Log("OAI Completion start 0 ");
memory = GetDefaultInformation();
Debug.Log("Start - memory: " + memory);
Debug.Log("OAI Completion start 1 ");
}
public void Brainwash(bool resetToDefault = true)
{
memory = resetToDefault ? GetDefaultInformation() : new StringBuilder();
}
/// <summary>
/// D
/// </summary>
/// <returns></returns>
protected virtual StringBuilder GetDefaultInformation()
{
Debug.Log("GetDefaultInformation - OAICompletion");
StringBuilder sb = new StringBuilder();
sb.Append(Description);
foreach (OAIBehavior behavior in GetComponents<OAIBehavior>())
{
string behaviorText = behavior.GetAsText();
sb.Append(behaviorText);
sb.Append(" ");
}
return sb;
}
public async void AddToStory(string value)
{
//QuestionReceivedEvent?.Invoke(value);
//Character should remember what they said before, since every time we send a request it requires the full 'story' to OpenAI
memory.Append(value).Append(InjectStartText);
QuestionReceivedEvent?.Invoke(memory.ToString());
if (logLevel == LogLevel.ResponsesAndMemory)
{
Debug.Log(memory);
}
if (!OAIEngine.Instance)
{
Debug.LogError("No OAIEngine object found in scene. Make sure there's a GameObject with an OAIEngine Component in your scene");
return;
}
//We allow the engine to change per request (= per character and per statement)
OAIEngine.Instance.Api.UsingEngine = GetEngine(engine);
if (NumOutputs < 1)
{
Debug.LogWarning($"NumOutputs was set to {NumOutputs}. You should have at least 1 output!");
NumOutputs = 1;
} else if (autoAddResponseToMemory && NumOutputs > 1)
{
Debug.Log("Multiple or no outputs are requested while autoAddResponseToMemory is still true. You should set this to false and manually call 'AddResponseToMemory' after selecting your prefered response.");
}
Debug.Log("Stop Seq: " + StopSequences[0] + " _ " + StopSequences[1]);
var c = new CompletionRequest(memory.ToString(), Max_tokens, Temperature, Top_p, NumOutputs, PresencePenalty, FrequencyPenalty, LogProbs, StopSequences);
var results = await OAIEngine.Instance.Api.Completions.CreateCompletionsAsync(c);
//ResponseReceivedEvent?.Invoke(results.Completions);
//We make it easy by auto-adding responses to the memory
if (autoAddResponseToMemory)
{
var r = results.Completions[0].Text;
AddResponseToMemory(r);
if (logLevel == LogLevel.Responses || logLevel == LogLevel.ResponsesAndMemory)
{
Debug.Log(r);
}
}
}
public void AddResponseToMemory (string value)
{
memory.Append(value).Append(InjectRestartText);
ResponseReceivedEvent1?.Invoke(memory.ToString());
Debug.Log("Memory: " + memory);
}
private Engine GetEngine(EngineEnum e)
{
switch (e)
{
case EngineEnum.Ada:
return Engine.Ada;
case EngineEnum.Babbage:
return Engine.Babbage;
case EngineEnum.Curie:
return Engine.Curie;
case EngineEnum.Davinci:
return Engine.Davinci;
}
return Engine.Default;
}
}
}
You should work with the text string that is passed to the function instead of the entire chat text. Assuming that all spoken dialog starts with "Dave: " you can easily detect this substring in the text, remove it and pass it to your text to speech.
using System;
string botName = "Dave: ";
public void Talk(string text){
chatText.text += "\n" + text;
if(text.StartsWith(botName)){
string spokenText = text.Remove(0, botName.Length)
_speaker.Speak(spokenText);
}
}

Xamarin forms - Cannot get object from REST API to xaml page

I am developing an Xamarin.Forms app in VS 2019. My REST API is hosted on GoDaddy.
When I call the api I get back my json converted object fine in my viewmodel. But the object is null
from my xaml page. See this code:
public class NewOrderViewModel : BaseViewModel
{
public NewOrderDetails NewOrderDetails { get; set; }
public ICommand OkCommand { get; private set;}
public ICommand CancelCommand { get; private set; }
readonly IPageService _pageService;
public NewOrderViewModel(IPageService pageService, int custId)
{
_pageService = pageService;
OkCommand = new Command(NewOrder);
CancelCommand = new Command(CancelOrder);
NewOrderDetails = new NewOrderDetails();
LoadNewOrderDetails(custId);
}
private async void LoadNewOrderDetails(int custId)
{
using (var client = new HttpClient(new System.Net.Http.HttpClientHandler()))
{
var response = await client.GetStringAsync("http://api.lates.com.au/api/Customers/" + custId.ToString());
var customer = JsonConvert.DeserializeObject<Customer>(response);
await _pageService.DisplayAlert("Value", customer.CustomerName, "OK"); //This confirms the correct customer is returned.
NewOrderDetails.CustomerName = customer.CustomerName;
foreach (var cd in customer.CustomerDepartments)
{
NewOrderDetails.CustomerDepartments.Add(cd);
}
NewOrderDetails.OrderDate = DateTime.Today;
NewOrderDetails.DeliveryDate = DateTime.Today;
NewOrderDetails.CustomerId = custId;
}
}
private void NewOrder()
{
_pageService.PopAsync();
_pageService.PushModalAsync(new CustomerOrder());
}
private void CancelOrder()
{
_pageService.PopAsync();
}
}
public partial class NewOrder : ContentPage
{
public NewOrder()
{
InitializeComponent();
imgAddIcon.Source = FileImageSource.FromFile("AddDocument64By64.png");
}
protected override void OnAppearing()
{
BindingContext = new NewOrderViewModel(new PageService(), 1);
//If i put a break point here the NewOrderDetails property of NewOrderViewModel is null - WHY???
}
}
It seems to be something to do with asynchronous timing. Let me know if you need more info.
Malcolm
If i put a break point here the NewOrderDetails property of
NewOrderViewModel is null - WHY???
At that time your break point hit, the data in NewOrderDetails has not be set because the httpRequest is still requesting and you have to await the request finish to get the data from Api.
To solve your problem, you have to implement INotifyPropertyChanged in both NewOrderDetails and NewOrderViewModel to notify the View update value after you get the data from Api. I will give you some code snippets:
In NewOrderDetails :
public class NewOrderDetails : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public NewOrderDetails()
{
}
public string CustomerName
{
set
{
if (customerName != value)
{
customerName = value;
OnPropertyChanged("CustomerName");
}
}
get
{
return customerName;
}
}
string customerName { get; set; }
}
In NewOrderViewModel :
public class NewOrderViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public NewOrderDetails NewOrderDetaila
{
set
{
if (newOrderDetails != value)
{
newOrderDetails = value;
OnPropertyChanged("NewOrderDetaila");
}
}
get
{
return newOrderDetails;
}
}
NewOrderDetails newOrderDetails { get; set; }
public NewOrderViewModel( int custId)
{
NewOrderDetaila = new NewOrderDetails();
LoadNewOrderDetails(custId);
}
private async void LoadNewOrderDetails(int custId)
{
//...
NewOrderDetaila.CustomerName = "133";
//...
}
}
And in Xaml binding:
<Label Text="{Binding NewOrderDetaila.CustomerName}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
Try and let me know if it works for you.
One problem in your code is here:
using (var client = new HttpClient(new System.Net.Http.HttpClientHandler()))
{
var response = await client.GetStringAsync("http://api.lates.com.au/api/Customers/" + custId.ToString());
var customer = JsonConvert.DeserializeObject<Customer>(response);
await _pageService.DisplayAlert("Value", customer.CustomerName, "OK"); //This confirms the correct customer is returned.
NewOrderDetails.CustomerName = customer.CustomerName;
foreach (var cd in customer.CustomerDepartments)
{
NewOrderDetails.CustomerDepartments.Add(cd);
}
NewOrderDetails.OrderDate = DateTime.Today;
NewOrderDetails.DeliveryDate = DateTime.Today;
NewOrderDetails.CustomerId = custId;
}
HttpClient should be defined as static class, and reused during your application lifetime. Disposing and recreating HttpClient leads to socket errors. Your code is causing multiple requests. I suggest also move this method to Task, that returns the object.
Example method:
internal class SendData
{
private static HttpClient client = new HttpClient();
internal static async Task<string> MakeServerRequest(string url, string content)
{
try
{
var request = new StringContent(content, Encoding.UTF8, "application/json");
var result = await client.PostAsync(url, request);
var response = await result.Content.ReadAsStringAsync();
return response;
}
catch (Exception ex)
{
YOUR ADDITIONAL LOGIC HERE
return null;
}
}
}
This will return JSON string that you can serialize to object, and do whatever your app requires.

how to make bot to search particular intent when a user selects particular selection using luis c# iam trying using switch case please guide me

iam trying to call particular intent when someone selects any intent eg:if user selects cmtools intent then the user can ask questions related to cmtools only. Other than cmtools if user asks question then the bot should rply Sorry not a good match.Please help me with the code to call intents from switch case or any other idea is appreciated.
Thanks in advance!
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Luis;
using Microsoft.Bot.Builder.Luis.Models;
using Newtonsoft.Json;
using System.Text;
namespace Microsoft.Bot.Sample.LuisBot
{
[LuisModel("id","key")]
[Serializable]
public class BasicLuisDialog : LuisDialog<object>
{
// QnA Maker global settings
// assumes all KBs are created with same Azure service
static string qnamaker_endpointKey = "endpointkey";
static string qnamaker_endpointDomain = "chatbot";
// QnA Maker Knowledge base
static string AIS_KB= "id1";
static string Speed_KB = "id2";
static string CMTools_KB = "id3";
// QnA Maker Finance Knowledge base
// Instantiate the knowledge bases
public QnAMakerService AIS_KBQnAService = new QnAMakerService("https://" + qnamaker_endpointDomain + ".azurewebsites.net", AIS_KB, qnamaker_endpointKey);
public QnAMakerService Speed_KBQnAService = new QnAMakerService("https://" + qnamaker_endpointDomain + ".azurewebsites.net", Speed_KB, qnamaker_endpointKey);
public QnAMakerService CMTools_KBQnAService = new QnAMakerService("https://" + qnamaker_endpointDomain + ".azurewebsites.net", CMTools_KB, qnamaker_endpointKey);
public const string AIS_KBIntent = "AIS_KB";
public const string Speed_KBIntent = "Speed_KB"; // new intent
public const string CMTools_KBIntent = "CMTools_KB"; // new intent
public class Metadata
{
public string name { get; set; }
public string value { get; set; }
}
public class Answer
{
public IList<string> questions { get; set; }
public string answer { get; set; }
public double score { get; set; }
public int id { get; set; }
public string source { get; set; }
public IList<object> keywords { get; set; }
public IList<Metadata> metadata { get; set; }
}
public class QnAAnswer
{
public IList<Answer> answers { get; set; }
}
[Serializable]
public class QnAMakerService
{
private string qnaServiceHostName;
private string knowledgeBaseId;
private string endpointKey;
public QnAMakerService(string hostName, string kbId, string endpointkey)
{
qnaServiceHostName = hostName;
knowledgeBaseId = kbId;
endpointKey = endpointkey;
}
async Task<string> Post(string uri, string body)
{
using (var client = new HttpClient())
using (var request = new HttpRequestMessage())
{
request.Method = HttpMethod.Post;
request.RequestUri = new Uri(uri);
request.Content = new StringContent(body, Encoding.UTF8, "application/json");
request.Headers.Add("Authorization", "EndpointKey " + endpointKey);
var response = await client.SendAsync(request);
return await response.Content.ReadAsStringAsync();
}
}
public async Task<string> GetAnswer(string question)
{
string uri = qnaServiceHostName + "/qnamaker/knowledgebases/" + knowledgeBaseId + "/generateAnswer";
string questionJSON = #"{'question': '" + question + "'}";
var response = await Post(uri, questionJSON);
var answers = JsonConvert.DeserializeObject<QnAAnswer>(response);
if (answers.answers.Count > 0)
{
return answers.answers[0].answer;
}
else
{
return "No good match found.";
}
}
}
public enum Selection
{
CMTools, AIS, Speed
}
[LuisIntent("AppSelection")]
private async Task AppSelection(IDialogContext context, LuisResult result)
{
var options = new Selection[] { Selection.CMTools, Selection.AIS, Selection.Speed };
var descriptions = new string[] { "CMTools", "AIS", "Speed" };
PromptDialog.Choice<Selection>(context, ResumeAfterOrderSelectionClarification,options, "Please choose your application?", descriptions: descriptions );
}
private async Task ResumeAfterOrderSelectionClarification(IDialogContext context, IAwaitable<Selection> result,LuisResult result1)
{
var selection = await result;
await context.PostAsync($"Thanks for choosing {selection}. how can I help you ?");
switch(selection){
case CMTools:
await context.PostAsync($"CMTools");
//code needed to call CMTools_KB intent
break;
case AIS:
await context.PostAsync($"AIS_KB");
//code needed to call AIS_KB intent
break;
case Speed:
await context.PostAsync($"Speed_KB");
// Ask the HR knowledge base
//code needed to call Speed_KB intent
break;
default:
await context.PostAsync($"testing.........");
}
}
[LuisIntent("")]
[LuisIntent("None")]
public async Task NoneIntent(IDialogContext context, LuisResult result)
{
HttpClient client = new HttpClient();
await this.ShowLuisResult(context, result);
}
[LuisIntent("AIS_KB")]
public async Task AIS_KBIntent(IDialogContext context, LuisResult result)
{
// Ask the HR knowledge base
var qnaMakerAnswer = await AIS_KBQnAService.GetAnswer(result.Query);
await context.PostAsync($"{qnaMakerAnswer}");
context.Wait(MessageReceived);
}
[LuisIntent("Speed_KB")]
public async Task Speed_KBIntent(IDialogContext context, LuisResult result)
{
// Ask the HR knowledge base
var qnaMakerAnswer = await Speed_KBQnAService.GetAnswer(result.Query);
await context.PostAsync($"{qnaMakerAnswer}");
context.Wait(MessageReceived);
}
[LuisIntent("CMTools_KB")]
public async Task CMTools_KBIntent(IDialogContext context, LuisResult result)
{
// Ask the HR knowledge base
var qnaMakerAnswer = await CMTools_KBQnAService.GetAnswer(result.Query);
await context.PostAsync($"{qnaMakerAnswer}");
context.Wait(MessageReceived);
}
private async Task ShowLuisResult(IDialogContext context, LuisResult result)
{
await context.PostAsync($"Sorry , I am not able to help you with this questions,Please connect our L2 Support Group");
var qnaMakerAnswer = await CMTools_KBQnAService.GetAnswer(result.Query);
await context.PostAsync($"{qnaMakerAnswer}");
await context.PostAsync($"it is finished ");
context.Wait(MessageReceived);
}
}
}

How to pass a class as parameter to Web Api

I want to pass a class as parameter to my Web API. Here is my code:
Web API:
[Route("api/[controller]")]
public class ValuesController : Controller
{
[HttpPost]
public string Post(ClusteringObject _cluesteringObject)
{
return _cluesteringObject.NumberOfCluster.ToString();
}
}
public class ClusteringObject
{
public string Data { get; set; }
public int NumberOfCluster { get; set; }
}
And my test console App code:
class Program
{
static void Main(string[] args)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:57961/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
var testData = new ClusteringObject()
{
Data = "asdf",
NumberOfCluster = 1
};
HttpResponseMessage response = client.PostAsJsonAsync("api/values", testData).Result;
string res = "";
using (HttpContent content = response.Content)
{
Task<string> result = content.ReadAsStringAsync();
res = result.Result;
}
}
}
public class ClusteringObject
{
public string Data { get; set; }
public int NumberOfCluster { get; set; }
}
My post action returns 0. It seems, I couldn't pass my object to Web API thats why it shows default values of each properties. How can I pass instance of ClusteringObject to my Web API ?
PostAsJsonAsync will send ClusteringObject as Body in the request, I suggest you try
FromBody like
public string Post([FromBody]ClusteringObject _cluesteringObject)

MEF Custom attributes and Lazy

I think I am losing my mind. :)
I've been struggling with this for two days now. The code looks right. But for some reason when I try to access the [ImportMany] field, it is null, or at least not returning any values.
It get the 3 parts in the catalog, but they don't get applied to the Lazy[] import I am defining.
Here's my code:
using System;
using System.Linq;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
namespace MefTest
{
// Extension interface and metadata
public interface IUIExtension
{
void DoSomething();
}
public interface IUIExtensionDetails
{
string Name { get; }
string Uri { get; }
}
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class UIExtensionAttribute : ExportAttribute
{
public UIExtensionAttribute() : base(typeof(IUIExtensionDetails)) { }
public string Name { get; set; }
public string Uri { get; set; }
}
// Extensions
[UIExtension(Name="Test 01", Uri="http://www.yourmomma.com/")]
public class Test1Extension : IUIExtension
{
public void DoSomething() { }
}
[UIExtension(Name = "Test 02", Uri = "http://www.yourdaddy.com/")]
public class Test2Extension : IUIExtension
{
public void DoSomething() { }
}
[UIExtension(Name = "Test 03", Uri = "http://www.youruncle.com/")]
public class Test3Extension : IUIExtension
{
public void DoSomething() { }
}
// Main program
public class Program
{
static void Main(string[] args)
{
Program p = new Program();
p.Run();
}
[ImportMany]
public Lazy<IUIExtension, IUIExtensionDetails>[] Senders { get; set; }
public void Run()
{
Compose();
}
public void Compose()
{
var catalog = new AssemblyCatalog(
System.Reflection.Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
// This is always 3
Console.WriteLine(
(from g in container.Catalog.Parts select g).Count());
// This is always 0
Console.WriteLine(Senders.Length);
Console.ReadKey();
}
}
}
Your error is here:
public UIExtensionAttribute() : base(typeof(IUIExtensionDetails))
You should pass the contract type there, not the metadata type:
public UIExtensionAttribute() : base(typeof(IUIExtension))
(Also, in order to make sure that your custom export class has the right properties as expected by the import with metadata, I would make it implement the IUIExtensionDetails interface. But that is not mandatory.)
Your metadata attribute is defining the exports as typeof(IUIExtensionDetails) which is your metadata contract, not your actual extension. Change the custom attribute constructor to:
public UIExtensionAttribute() : base(typeof(IUIExtension)) { }