Azure SDK UnauthorizedException: Put token failed. status-code: 401, status description: Unauthorized: When KeyName is empty the resource URI must - c#

Using C#, the Windows Form Template from Visual Studio and the Microsoft Azure Client SDK
I'm trying to send a message to an IOT Hub. I tested connection String with a Virtual Pi and it works there, I can see the connection and incoming messages in the Azure Shell.
Imports:
using System.Windows.Forms;
using Microsoft.Azure.Devices;
using Message = Microsoft.Azure.Devices.Message;
static string connectionString = "HostName=****.azure- devices.net;DeviceId=****;SharedAccessKey=*******=";
static string deviceId = "SensorTest";
static ServiceClient serviceClient;
serviceClient = ServiceClient.CreateFromConnectionString(connectionString);
SendMessageToCloud("takemystringyoupieceof***").Wait();
private static async Task SendMessageToCloud(string s)
{
MyData data = new MyData
{
Thing1 = "string",
Thing2 = "string",
Thing3 = 1234,
Thing4 = "string",
Thing5 = s
};
var serializeData = JsonConvert.SerializeObject(data);
var commandMessage = new Message(Encoding.ASCII.GetBytes(serializeData));
await serviceClient.SendAsync(deviceId, commandMessage);
}
This throws an Inner Exception:
{"Put token failed. status-code: 401, status-description:
Unauthorized: When KeyName is empty the resource URI must include a device id (Value '****.azure- devices.net').."}
System.Exception {Microsoft.Azure.Devices.Common.Exceptions.UnauthorizedException}
I would like some help understanding the error message:
Isn't KeyName "SharedKey" in this instance?`
The deviceID is included as you can see above?
The Value in the error message is the value for hostname?
For working code example, myData class:
internal class MyData
{
public string Thing1 { get; set; }
public string Thing2 { get; internal set; }
public int Thing3 { get; set; }
public string Thing4 { get; set; }
public string Thing5 { get; set; }
}

You are using a DeviceClient connection string with the ServiceClient. The SendAsync you are calling in your SendMessageToCloud is actually a Cloud to Device API. So depending on your intention, the answer is you either need to use the DeviceClient (a helpful sample can be found here) or you want to use an appropriate Service connection string which can be found in the Shared access policies blade for the IoTHub in the portal. Practicing least privilege, the Service key allows cloud-to-device messages (details on shared access policies can be found here)

Related

How can I pass configuration values to a KafkaTrigger function using a separate KafkaClusterOptions class in Azure Functions?

I have a KafkaClusterOptions class in my Azure Functions project that contains the configuration values for connecting to a Kafka cluster:
public class KafkaClusterOptions
{
[ConfigKey("MP/Kafka/BootstrapServer")]
public string BootstrapServer { get; set; }
[ConfigKey("MP/section/Kafka/SaslUsername")]
public string SaslUsername { get; set; }
[ConfigKey("MP/section/Kafka/SaslPassword")]
public string SaslPassword { get; set; }
}
I also have a KafkaTrigger function that needs to connect to the Kafka cluster:
public Task KafkaTrigger(
[KafkaTrigger(
BootstrapServer = "<bootstrap-server>",
TopicName = "<topic-name>",
ConsumerGroup = "<consumer-group>",
SslCaLocation = "confluent_cloud_cacert.pem",
Protocol = BrokerProtocol.SaslSsl,
AuthenticationMode = BrokerAuthenticationMode.Plain,
Username = "<sasl-username>",
Password = "<sasl-password>",
IsBatched = true)]
string[] kafkaEventsStr,
FunctionContext context)
{
// Process Kafka events
}
I would like to pass the configuration values from the KafkaClusterOptions class to the KafkaTrigger function instead of hard-coding them in the attribute. Can someone provide an example of how to inject the KafkaClusterOptions instance into the KafkaTrigger function using dependency injection, and how to use the configuration values in the KafkaTrigger attribute?
Thank you!

How to connect SAP Business One Service Layer using .NET Framework

I am unable to connect SAP Business One Service Layer using .NET Framework. It throws the error Internal Server Error with status code 500.
HttpClient client = new HttpClient();
string json = JsonConvert.SerializeObject(new {
CompanyDB = "company",
UserName = "username",
Password = "password"
}
);
var response = await client.PostAsync("service layer URL", new StringContent(json,
Encoding.Default, "application/json"));
Although I am able to connect to the service layer using the .NET Core application with the same code.
I have gone through this blog https://blogs.sap.com/2015/07/15/how-to-consume-service-layer-odata-services-from-net-via-wcf/
But I am unable to Add Service Reference for the service layer.
I am not sure whether this problem belongs to the service layer or my program. Do we need to do extra code to call the OData service (as the service layer is oData V3 or V4) from the .net framework?
Please help me to solve this problem.
Regarding your login problem you could intercept the sent HTTP messages from both of your applications (.NET Framework vs. .NET Core) and compare them. For example by using Fiddler.
You might find a little difference which prevents SL from processing your request.
I was able to connect to SL successfully in a .NET Core project by using the RestSharp library. Maybe this will get you started:
private static void Main(string[] args)
{
var client = new RestClient("https://<host>:<port>/b1s/v2");
client.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
// allow all certificates for testing purposes
return true;
};
var resolver = new CamelCasePropertyNamesContractResolver();
// respect the names defined in the JsonProperty annotations
resolver.NamingStrategy.OverrideSpecifiedNames = false;
client.UseNewtonsoftJson(new JsonSerializerSettings() { ContractResolver = resolver });
var loginRequest = new RestRequest("Login");
loginRequest.AddJsonBody(new LoginBody() { CompanyDB = "yourCompanyDb", UserName = "user1", Password = "myPassword" });
var response = client.Post<LoginResponse>(loginRequest);
if (response.Data != null)
{
var itemRequest = new RestRequest("Items('57387')"); // get an item
// add the session cookie
itemRequest.AddHeader("Cookie", $"B1SESSION={response.Data.SessionId}");
var itemResponse = client.Get<Items>(itemRequest);
}
}
The JSON POCOs:
public class LoginBody
{
[JsonProperty("CompanyDB")]
public string CompanyDB { get; set; }
[JsonProperty("Password")]
public string Password { get; set; }
[JsonProperty("UserName")]
public string UserName { get; set; }
}
public class LoginResponse
{
[JsonProperty("SessionId")]
public string SessionId { get; set; }
}
public class Items
{
[JsonProperty("ItemCode")]
public string ItemCode { get; set; }
[JsonProperty("ItemName")]
public string ItemName { get; set; }
}
Regarding your question to add a Service Reference I am not sure what you want to achieve. If you want to generate a OData client along with all required classes and data models you might want to look at the Unchase OData Connected Service Visual Studio extension. For me this worked well since it generates a functioning SL/OData client based on the Microsoft.OData.Client.DataServiceContext class. Please see the OData client documentation for more examples on how too use it.
I wrote a library called B1SLayer that aims to make Service Layer requests a lot easier with .NET, have a look:
https://github.com/bgmulinari/B1SLayer/
https://www.nuget.org/packages/B1SLayer/
Just create your SLConnection instance and the session is managed automatically. Check the GitHub page for request samples.
var serviceLayer = new SLConnection("https://sapserver:50000/b1s/v1", "CompanyDB", "manager", "12345");

Send email with default email client from a WPF app

I am trying to find a reliable way to be able to send emails using the system default external mail client from my WPF app.
I know similar questions are already posted on stackoverflow (Open default mail client along with a attachment), but they are outdated and I couldn't find any solution which is robust enough.
My requirements:
Works from a WPF desktop app on W10
Use the external email client which is the user's default app for Email
Works with the most popular email apps (Outlook, Thunderbird,
the Mail app in Windows)
Attachments
HTML, plain text and RTF body
The email appears in the drafts folder and recognized as a new, unsent message by the clients
The things I've found so far:
mailto links: no-go because no attachment support
EML file: issues like the from field has to be explicitly set and also the message is not marked as a new unsent message by some clients even if the "X-Unsent: 1" is set
MAPI: Depricated by MS and they themselves do not recommend using it. People complaining about several bugs and issues. Also the default mail client is not recognized properly on Windows 10.
So far MAPI seems to be the best option but honestly I don't like any of them but I couldn't find any better alternative.
You can simply use Mailkit
It works from any app, which is using .NET.
It supports HTML, plain text and also RTF body
Yes, the email appears in the drafts folder and recognized as a new, you can achieve it.
It supports attachments
Usage is quite simple, I can give you an example of how I am using it in my hobby project.
I am not using attachments, but with Mailkit, it is very easy to do that.
You can check their FAQ
But anyway I will post here how I am using it.
1) First create message class, which will be responsible to handle Subject, content and mail info about the person you are sending the email.
With the simple constructor, you will seed put into your properties.
using System;
using MimeKit;
namespace SomeNamespace
{
public class Message
{
public List<MailboxAddress> To { get; set; }
public string Subject { get; set; }
public string Content { get; set; }
public Message(IEnumerable<string> to, string subject, string content)
{
To = new List<MailboxAddress>();
To.AddRange(to.Select(x => new MailboxAddress(x)));
Subject = subject;
Content = content;
}
}
}
2) You will need also your SMTP configuration class, which will have information about your email. (From where you will send mails)
using System;
namespace SomeNamespace
{
public class EmailConfiguration
{
public string SMTPFrom { get; set; }
public string SMTPHost { get; set; }
public int SMTPPort { get; set; }
public string SMTPLogin { get; set; }
public string SMTPPassword { get; set; }
}
}
3) Then you will create your simple Interface
public interface IEmailSender
{
void SendEmail(Message message);
}
4) Then you will create an implementation of your service.
using System;
using MailKit.Net.Smtp;
using MimeKit;
using SomeNamespace.Configuration;
using SomeNamespace.Services.Interfaces;
namespace SomeNamespace
{
public class EmailSender : IEmailSender
{
private readonly EmailConfiguration _emailConfig;
public EmailSender(EmailConfiguration emailConfig)
{
_emailConfig = emailConfig;
}
public void SendEmail(Message message)
{
var emailMessage = CreateEmailMessage(message);
Send(emailMessage);
}
private MimeMessage CreateEmailMessage(Message message)
{
var emailMessage = new MimeMessage();
emailMessage.From.Add(new MailboxAddress(_emailConfig.SMTPFrom));
emailMessage.To.AddRange(message.To);
emailMessage.Subject = message.Subject;
emailMessage.Body = new TextPart(MimeKit.Text.TextFormat.Text) { Text = message.Content };
return emailMessage;
}
private void Send(MimeMessage mailMessage)
{
using (var client = new SmtpClient())
{
try
{
client.Connect(_emailConfig.SMTPHost, _emailConfig.SMTPPort, true);
client.AuthenticationMechanisms.Remove("XOAUTH2");
client.Authenticate(_emailConfig.SMTPLogin, _emailConfig.SMTPPassword);
client.Send(mailMessage);
}
catch(Exception message)
{
Console.WriteLine(message);
throw;
}
finally
{
client.Disconnect(true);
client.Dispose();
}
}
}
}
}
The last step is to create your Message and EmailConfiguration models.
Create the instance of the EmailSender and send the email.
var message = new Message("whotosend#gmail.com", "Some example Subject", "Some example content");
var emailConfiguration = new EmailConfiguration()
{
SMTPFrom = "yourSmtpAdress",
SMTPHost = "yourSmtpHost",
SMTPLogin = "yourSmtpLogin",
SMTPPassword = "yourSmtpPassword",
SMTPPort = 465
};
var _emailSender = new EmailSender(emailConfiguration);
_emailSender.SendEmail(message);
Of course, you can use DI to create your service instances but it's up to you.
Stay home, stay safe, wish you happy coding.

Why is C# and MongoDB Driver code connecting, but failing to Write?

Here is the information about my development environment:
Microsoft Visual Studio Community 2015
.NET Framework 4.6
ASP.NET MVC assembly System.Web.Mvc Version=5.2.3.0
MongoDB.Driver 2.0.1.27
Mongodb 3.0.6
Within my C# application, I have the following code that retrieves a MongoDB database reference:
public class MongoDBConnectionManager {
public IMongoDatabase getMongoDB() {
var client = new MongoClient("mongodb://localhost:27017");
MongoClient(System.Configuration.ConfigurationManager.ConnectionStrings["MongoDB"].ConnectionString);
MongoServer.Create("Server=localhost:27017");
IMongoCollection <BsonDocument> UserDetails = iMgDb.GetCollection<BsonDocument>("Users");
return iMgDb;
}
}
Here is the POCO class that represent a User Business Entity:
using MongoDB.Bson.Serialization.Attributes;
public class UserModel {
[BsonId]
public int ID { get; set; }
[Required]
[BsonElement]
public string UserName { get; set; }
[Required]
[BsonElement]
public string Password { get; set; }
[Required]
[BsonElement]
public string Email { get; set; }
[BsonElement]
public string PhoneNo { get; set; }
[BsonElement]
public string Address { get; set; }
}
Here is the DAO C# class that uses the Mongo DB Connection Manager Class:
public class DAO {
public async Task<int> insertNewUser(UserModel um) {
MongoDBConnectionManager mgoDBCntMng = new MongoDBConnectionManager();
IMongoDatabase database = mgoDBCntMng.getMongoDB();
IMongoCollection <UserModel> UserDetails = database.GetCollection<UserModel>("Users");
try {
Task getTask = UserDetails.InsertOneAsync(um);
await getTask;
} catch(Exception) {
}
return 0;
}
}
When I run the application, I can see the following information logged in the DOS Command Prompt window where I started the mongoDB. If you look towards the end of the Dos Command Prompt, you will notice 2 connections being made:
C:\Program Files\MongoDB\Server\3.0\bin>mongod --dbpath ./data/db
2015-09-23T12:23:07.896+0530 I JOURNAL [initandlisten] journal
dir=./data/db\jo
urnal
2015-09-23T12:23:07.900+0530 I JOURNAL [initandlisten] recover : no
journal fil
es present, no recovery needed
2015-09-23T12:23:08.060+0530 I JOURNAL [durability] Durability thread started
2015-09-23T12:23:08.062+0530 I JOURNAL [journal writer] Journal writer thread s
tarted
2015-09-23T12:23:08.283+0530 I CONTROL [initandlisten] MongoDB starting
: pid=1
2936 port=27017 dbpath=./data/db 64-bit host=My-PC
2015-09-23T12:23:08.283+0530 I CONTROL [initandlisten] targetMinOS:
Windows 7/W
indows Server 2008 R2
2015-09-23T12:23:08.284+0530 I CONTROL [initandlisten] db version v3.0.6
2015-09-23T12:23:08.284+0530 I CONTROL [initandlisten] git version:
1ef45a23a4c
5e3480ac919b28afcba3c615488f2
2015-09-23T12:23:08.284+0530 I CONTROL [initandlisten] build info:
windows sys.
getwindowsversion(major=6, minor=1, build=7601, platform=2,
service_pack='Servic
e Pack 1') BOOST_LIB_VERSION=1_49
2015-09-23T12:23:08.285+0530 I CONTROL [initandlisten] allocator:
tcmalloc
2015-09-23T12:23:08.285+0530 I CONTROL [initandlisten] options: {
storage: { db
Path: "./data/db" } }
2015-09-23T12:23:08.321+0530 I NETWORK [initandlisten] waiting for
connections
on port 27017
2015-09-23T12:24:20.326+0530 I NETWORK [initandlisten] connection
accepted from
127.0.0.1:65065 #1 (1 connection now open)
2015-09-23T12:24:22.332+0530 I NETWORK [initandlisten] connection
accepted from
127.0.0.1:65066 #2 (2 connections now open)
I'm really stumped as to how to resolve the problem. I tried to search the MongoDB error log using DOS command prompt, but it shows NO errors.
From MongoDB client using DOS command prompt, I got the following:
C:\Program Files\MongoDB\Server\3.0\bin>mongo
MongoDB shell version: 3.0.6
connecting to: test
> use foo
switched to db foo
> db.runCommand( { getLastError: 1, w: 1, wtimeout:5000 } )
{
"connectionId" : 6,
"n" : 0,
"syncMillis" : 0,
"writtenTo" : null,
"err" : null,
"ok" : 1
}
The problem that I am facing is that the point of execution runs smoothly, but fails to write to the database.
What is wrong with the way I use async and wait in the said code?
Could someone please tell me how to correct the problem?
From MSDN
The await operator is applied to a task in an asynchronous method to suspend the execution of the method until the awaited task completes. The task represents ongoing work.
So await does suspend or block until the task is completed so we just create multiple tasks and make sure we don't await it until we think we need to, if you are dealing with collection of inserts?
var tasks = new Task<//return type of UserDetails.InsertOneAsync(um)>[//get collection count here];
var count = 0;
foreach(// iterate collection here)
{
try {
tasks[count] = UserDetails.InsertOneAsync(um); // adds continuations and return back immediately i.e. no blocking
count++;
} catch(Exception) {
}
}
await Task.WhenAll(tasks.ToArray()); // here is where we block all tasks and wait for completion
Note: Not exactly answer but will somewhat clear what we are doing
There are some problem with your getMongoDB function code:
No need of making two connection use only one
IMongoClient is used to get database in Mongo.Driver 2.0.1.17. No need of Making "MongoServer"
No need of getting collection in "getMongoDB" function
Here is the code for "getMongoDB" function:
public IMongoDatabase getMongoDB()
{
IMongoClient client = new MongoClient("mongodb://localhost:27017");
var iMgDb = client.GetDatabase("Database Name Here");
return iMgDb;
}
Hope this will help.
It took a while, but the problem was caused by the fact that I mistakenly used the int basic data type for the ID in the UserModel as opposed to ObjectId.
Here is the corrected code for the UserModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using WebApplication1.Models;
using MongoDB.Bson.Serialization.Attributes;
namespace WebApplication1.Models
{
public class UserModel
{
[BsonId]
public ObjectId ID { get; set; }
[Required]
[BsonElement]
public string UserName { get; set; }
[Required]
[BsonElement]
public string Password { get; set; }
[Required]
[BsonElement]
public string Email { get; set; }
[BsonElement]
public string PhoneNo { get; set; }
[BsonElement]
public string Address { get; set; }
}
}

Amazon Simple Notification Service with custom iOS payload not so simple

Sending a plain text notification is easy and well documented. But I've been pulling my hair today regarding sending a custom notification for iOS that has the alert and some fields like userId.
I started with this help page and implemented something similar to the last sample, then I found this answer that seems to invalidate the last sample on the help page, as the "url" property should be outside the "aps" object. I tried a good deal of combinations but each one of them gets sent as text to the app (the whole message, with the "default" property and "APNS" object)...
If I explicitly set MessageStructure to json I get the error: "Invalid parameter: Message Structure - JSON message body failed to parse" but I'm pretty sure my JSON is good, when sent to SNS the string in the Message property looks like this:
{ "default":"You received a new message from X.",
"APNS_SANDBOX":"{ \"aps\": {\"alert\":\"You received a new message from X.\"},
\"event\":\"Message\",
\"objectID\":\"7a39d9f4-2c3f-43d5-97e0-914c4a117cee\"
}",
"APNS":"{ \"aps\": {\"alert\":\"You received a new message from X.\"},
\"event\":\"Message\",
\"objectID\":\"7a39d9f4-2c3f-43d5-97e0-914c4a117cee\"
}"
}
Does anybody have a good example of sending a notification with custom payload through SNS in C#? Because Amazon sure hasn't...
Strangely when I implemented the clean way of doing this by using classes and serializing objects instead of just sending a formatted string it worked. The only difference was the spacing... in the clean version there are no spaces except in the property values:
{"default":"You received a new message from X.","APNS_SANDBOX":"{\"aps\":{\"alert\":\"You received a new message from X.\"},\"event\":\"Message\",\"objectID\":\"7a39d9f4-2c3f-43d5-97e0-914c4a117cee\"}","APNS":"{\"aps\":{\"alert\":\"You received a new message from X.\"},\"event\":\"Message\",\"objectID\":\"7a39d9f4-2c3f-43d5-97e0-914c4a117cee\"}"}
These are the classes that I'm serializing (only for APNS for the moment), use whatever properties you need instead of Event and ObjectID:
[DataContract]
public class AmazonSNSMessage
{
[DataMember(Name = "default")]
public string Default { get; set; }
[DataMember(Name = "APNS_SANDBOX")]
public string APNSSandbox { get; set; }
[DataMember(Name = "APNS")]
public string APNSLive { get; set; }
public AmazonSNSMessage(string notificationText, NotificationEvent notificationEvent, string objectID)
{
Default = notificationText;
var apnsSerialized = JsonConvert.SerializeObject(new APNS
{
APS = new APS { Alert = notificationText },
Event = Enum.GetName(typeof(NotificationEvent), notificationEvent),
ObjectID = objectID
});
APNSLive = APNSSandbox = apnsSerialized;
}
public string SerializeToJSON()
{
return JsonConvert.SerializeObject(this);
}
}
[DataContract]
public class APNS
{
[DataMember(Name = "aps")]
public APS APS { get; set; }
[DataMember(Name = "event")]
public string Event { get; set; }
[DataMember(Name = "objectID")]
public string ObjectID { get; set; }
}
[DataContract]
public class APS
{
[DataMember(Name = "alert")]
public string Alert { get; set; }
}
So I get the Amazon SNS message by doing:
new AmazonSNSMessage(...).SerializeToJSON();
The key that just fixed this for me was realizing that the outer JSON specific to SNS (e.g. the "default" and "APNS" properties) MUST NOT be escaped, ONLY the inner payload. For instance, this Message value succeeded (just posting the start):
{"APNS":"{\"aps\" ...
Notice the first property "APNS" is not escaped, but then it's value (your actual payload that will hit the device) IS escaped. The following gets the job done:
JObject payload = ... // your apns, etc payload JObject
var snsMsgJson = new JObject(
new JProperty("default", "message 1 2 3"), // "default" is optional if APNS provided
new JProperty("APNS", payload.ToString(Formatting.None))
);
string str = snsMsgJson.ToString(Formatting.None);
I figured this out looking at the example above, thanks! But, I knew the above 'clean classes' so-called solution couldn't and shouldn't actually be a requirement. So when he says: "The only difference was the spacing... in the clean version there are no spaces except in the property values" that is not correct. The real difference is as I said, outer (SNS specific) JSON shall not be escaped, but inner must.
Soap box: How about that documentation eh? So many things in this API that wasted major time and just as importantly: one's sense of well-being. I do appreciate the service though regardless.

Categories