WCF Service - queue & batch messages (sending emails via SMTP) - c#

I have a WCF Service working via named pipes that is receiving data from ASP.NET MVC application:
[ServiceContract]
public interface IEmailProcessor
{
[OperationContract(AsyncPattern = true)]
void SendEmails(string subject, string body, string from, string to);
}
Service gets subject, body, sender and recipients of an email. Then it puts an email together and send via SMTP.
Client (MVC applicataion) is sending several emails at once, so I would like the service itself to work asynchronously (client just invokes SendEmails method few times and then the WCF Server takes care of the rest). But my email server sometimes refuses to send email because of greylisting of new email addresses. That's why I would like the WCF Server to also queue those email and try to send them one by one (retry few times if the error occurs).
I've read several topics on WCF async nad MSMQ methods, but what is the best approach in my situation? Should I create two WCF Services (client-server & server-server)? Or maybe use multi-threading? It's also important for me to not use any built-in SMTP solution because I would like to expand my Service to handle other messages not only emails.

For exactly the same requirement I've made this whole Email solution for my self however I did not use the MSMQ queue.
following are the steps
Create Asynchronous WCF web service
Use Task await async technique to run the send email thread inside service using task.run(()=>AsyncSendEmail)
skip through on all smptpexception on try catch and run sp to update table field
isEmailSent= false
in case of error
subscribe the web servive through client "yourwebapplication"
invoke service by doing task.factory.startNew(()=> proxy.sendEmail(paramters))
create a simple windows task scheduler task to invoke to run service on scheduled time period to retry sending emails Simple Window Task Schedular
To read no deliver, failure email Emails from your exchange account I used third party service component Admin System Software and update table fields isDelivered=No , ErrorDescription=errorDesc
Hope this solution helps you..
Shaz

I followed zaitsman's suggestion and created queue inside the service using ConcurrentQueue. I also used this example of creating new Thread: http://msdn.microsoft.com/en-us/library/7a2f3ay4%28v=vs.80%29.aspx
I added new Thread to my WCF Server:
var thread = new Thread(EmailThread.Instace.DoWork);
thread.Start();
And then SendEmails method in my Service uses Enqueue to add msg to the ConcurrentQueue.
public void SendEmails(string from, string to, string subject, string body)
{
var msg = new MailMessage(from, to, subject, body)
{
IsBodyHtml = true,
BodyEncoding = Encoding.UTF8,
DeliveryNotificationOptions = DeliveryNotificationOptions.OnFailure
};
EmailThread.Instace.Messages.Enqueue(msg);
}
And here is EmailThread class that is doing it's work in the background (ie. accepting new emails to the queue and sequentially dequeuing them.
internal class EmailThread
{
//Ensure that only one instance of EmailThread can exist
private static readonly EmailThread Instance = new EmailThread();
public static EmailThread Instace { get { return Instance; } }
//Here is our Queue
public ConcurrentQueue<MailMessage> Messages { get; private set; }
private EmailThread()
{
Messages = new ConcurrentQueue<MailMessage>();
}
// This method is called when the thread is started and repeated once a 10 seconds
public void DoWork()
{
while (!_shouldStop)
{
Console.WriteLine("email sender thread: working...");
if (!Messages.IsEmpty)
{
//define SMTP credentials here
var smtp = new SmtpClient()
var failed = new Queue<MailMessage>();
MailMessage message;
while (Messages.TryDequeue(out message))
{
try
{
smtp.Send(message);
Console.WriteLine("email sender thread: successfully sent email...");
}
catch (SmtpException)
{
//Enqueue again if failed
failed.Enqueue(message);
Console.WriteLine("email sender thread: error sending email, enqueuing...");
}
}
foreach (var mailMessage in failed)
{
Messages.Enqueue(mailMessage);
}
smtp.Dispose();
}
Thread.Sleep(10000);
}
Console.WriteLine("email sender thread: terminating gracefully.");
}
public void RequestStop()
{
_shouldStop = true;
}
// Volatile is used as hint to the compiler that this data
// member will be accessed by multiple threads.
private volatile bool _shouldStop;
}

Related

Directing messages to consumers

My client is attempting to send messages to the receiver. However I noticed that the receiver sometimes does not receive all the messages sent by the client thus missing a few messages (not sure where the problem is ? Client or the receiver).
Any suggestions on why that might be happening. This is what I am currently doing
On the receiver side this is what I am doing.
This is the Event Processor
async Task IEventProcessor.ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> messages)
{
foreach (var eventData in messages)
{
var data = Encoding.UTF8.GetString(eventData.Body.Array, eventData.Body.Offset, eventData.Body.Count);
}
}
This is how the client connects to the event hub
var StrBuilder = new EventHubsConnectionStringBuilder(eventHubConnectionString)
{
EntityPath = eventHubName,
};
this.eventHubClient = EventHubClient.CreateFromConnectionString(StrBuilder.ToString());
How do I direct my messages to specific consumers
I'm using this sample code from eventhub official doc, for sending and receiving.
And I have 2 consumer groups: $Default and newcg. Suppose you have 2 clients, the client_1 are using the default consumer group($Default), and client_2 are using the other consumer group(newcg)
First, after create the send client, in the SendMessagesToEventHub method, we need to add a property with value. The value should be the consumer group name. Sample code like below:
private static async Task SendMessagesToEventHub(int numMessagesToSend)
{
for (var i = 0; i < numMessagesToSend; i++)
{
try
{
var message = "444 Message";
Console.WriteLine($"Sending message: {message}");
EventData mydata = new EventData(Encoding.UTF8.GetBytes(message));
//here, we add a property named "cg", it's value is the consumer group. By setting this property, then we can read this message via this specified consumer group.
mydata.Properties.Add("cg", "newcg");
await eventHubClient.SendAsync(mydata);
}
catch (Exception exception)
{
Console.WriteLine($"{DateTime.Now} > Exception: {exception.Message}");
}
await Task.Delay(10);
}
Console.WriteLine($"{numMessagesToSend} messages sent.");
}
Then in the client_1, after create the receiver project, which use the default consumer group($Default)
-> in the SimpleEventProcessor class -> ProcessEventsAsync method, we can filter out the unnecessary event data. Sample code for ProcessEventsAsync method:
public Task ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> messages)
{
foreach (var eventData in messages)
{
//filter the data here
if (eventData.Properties["cg"].ToString() == "$Default")
{
var data = Encoding.UTF8.GetString(eventData.Body.Array, eventData.Body.Offset, eventData.Body.Count);
Console.WriteLine($"Message received. Partition: '{context.PartitionId}', Data: '{data}'");
Console.WriteLine(context.ConsumerGroupName);
}
}
return context.CheckpointAsync();
}
And in another client, like client_2, which use another consumer group, like it's name is newcg, we can follow the steps in client_1, just a little changes in ProcessEventsAsync method, like below:
public Task ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> messages)
{
foreach (var eventData in messages)
{
//filter the data here, using another consumer group name
if (eventData.Properties["cg"].ToString() == "newcg")
{
//other code
}
}
return context.CheckpointAsync();
}
This happens only when there are 2 or more Event Processor Host reading from same consumer group.
If you have event hub with 32 partitions and 2 event processor host reading from same consumer group. Then each event processor host will read from 16 partition and so on.
Similarly if 4 Event processor host parallelly reading from same consumer group then each will read from 8 partitions.
Check if you have 2 or more event processor host running on same consumer group.
I have tested your code and slightly modified it(different overload of EventProcessorHost constructor, and added CheckpointAsync after consuming the messages), and then did some tests.
By using the default implementation and default EventProcessorOptions(EventProcessorOptions.DefaultOptions) I can say that I did experience some latency when it comes to consuming messages, but all messages were processed successfully.
So, sometimes it seems like I am not getting the messages from the certain partition, but after a certain period of time, all messages arrive:
Here you can find the actual modified code that worked for me. It is a simple console app that prints to the console if something arrives.
string processorHostName = Guid.NewGuid().ToString();
var Options = new EventProcessorOptions()
{
MaxBatchSize = 1, //not required to make it working, just for testing
};
Options.SetExceptionHandler((ex) =>
{
System.Diagnostics.Debug.WriteLine($"Exception : {ex}");
});
var eventHubCS = "event hub connection string";
var storageCS = "storage connection string";
var containerName = "test";
var eventHubname = "test2";
EventProcessorHost eventProcessorHost = new EventProcessorHost(eventHubname, "$Default", eventHubCS, storageCS, containerName);
eventProcessorHost.RegisterEventProcessorAsync<MyEventProcessor>(Options).Wait();
For sending the messages to the event hub and testing I used this message publisher app.

Signalr - It's possible to wait reponse from client?

I am a beginner in using Signalr and am checking out some examples.
Is it possible to send a message to the client from the server and wait for a return from it? Or is it possible to guarantee that after the answer the same session will be used?
My question is because in a given process, within a transaction, I need to ask the user if he wants to continue with the changes. I have not been able to ask this question before because validations should be done in the same session where changes have been made (but not yet confirmed).
Reiterating the comment from Jaime Yule, WebSockets are bidirectional communication and do not follow the Request/Response architecture for messaging. Given the very fluid nature of communication around WebSockets, these bullet points are good to keep in mind for your current (& future) scenarios:
SignalR is great if you're going to use it for fire & forget (Display a pop-up to a user and that's it)
It's not designed around request-response like you're asking, and trying to use it as such is an anti-pattern
Messages may be sent from either end of the connection at any time,
and there is no native support for one message to indicate it is
related to another
This makes the protocol poorly suited for transactional requirements
It is possible, but i would not recommend (relying on) it.
And it's not a pretty solution (using a static event and being pretty complex for such a simple thing).
Story goes like this:
Make sure client and server know the connectionId - They probably know that already, but i could not figure out a way to access it.
Await NotificationService.ConfirmAsync
... which will call confirm on the client
... which will await the user supplied answer
... and send it back to the server using Callback from The hub.
... which will notify the Callback from the NotificationService over a static event
... which will hand off the message back to ConfirmAsync (using a AutoResetEvent)
... which is hopefully still waiting :)
Client and server both have a 10 second timeout set.
The hub:
// Setup as /notification-hub
public class NotificationHub : Hub {
public string ConnectionId() => Context.ConnectionId;
public static event Action<string, string> Response;
public void Callback(string connectionId, string message) {
Response?.Invoke(connectionId, message);
}
}
Service:
// Wire it up using DI
public class NotificationService {
private readonly IHubContext<NotificationHub> _notificationHubContext;
public NotificationService(IHubContext<NotificationHub> notificationHubContext) {
_notificationHubContext = notificationHubContext;
}
public async Task<string> ConfirmAsync(string connectionId, string text, IEnumerable<string> choices) {
await _notificationHubContext.Clients.Client(connectionId)
.SendAsync("confirm", text, choices);
var are = new AutoResetEvent(false);
string response = null;
void Callback(string connId, string message) {
if (connectionId == connId) {
response = message;
are.Set();
}
}
NotificationHub.Response += Callback;
are.WaitOne(TimeSpan.FromSeconds(10));
NotificationHub.Response -= Callback;
return response;
}
}
Client side js:
var conn = new signalR.HubConnectionBuilder().withUrl("/notification-hub").build();
var connId;
// using Noty v3 (https://ned.im/noty/)
function confirm(text, choices) {
return new Promise(function (resolve, reject) {
var n = new Noty({
text: text,
timeout: 10000,
buttons: choices.map(function (b) {
return Noty.button(b, 'btn', function () {
resolve(b);
n.close();
});
})
});
n.show();
});
}
conn.on('confirm', function(text, choices) {
confirm(text, choices).then(function(choice) {
conn.invoke("Callback", connId, choice);
});
});
conn.start().then(function() {
conn.invoke("ConnectionId").then(function (connectionId) {
connId = connectionId;
// Picked up by a form and posted to the server
document.querySelector(".connection-id-input").value = connectionId;
});
});
For me this is way to complex to put it into the project i am working on.
It really looks like something that will come back and bite you later...
Is it possible to send a message to the client from the server and wait for a return from it? Or is it possible to guarantee that after the answer the same session will be used?
None of this is possible. Currently there's no way to wait for the client's response or even to get to know if the client received the message. There's some discussion implementing this on GitHub. Also here's the feature request.
Until then, the workaround is to send a "notification" from the server with a fire and forget attitude and let the client get the required data via a HTTP request to the server.
This is now possible with .NET 7 using Client Results.
Today, I've highlighted this issue in dotnet's Github page and got a good response from one of the developers of SignalR.
This requires the server to use ISingleClientProxy.InvokeAsync to be able to make request to the client and wait for response.
Quote from the documentation
In addition to making calls to clients, the server can request a
result from a client. This requires the server to use
ISingleClientProxy.InvokeAsync and the client to return a result from
its .On handler.
From the client (js/ts)
hubConnection.on("GetMessage", async () => {
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(new { data: "message" });
}, 100);
});
return promise;
});
From the server (C#)
//By calling Client(...) on an instance of IHubContext<T>
async Task<object> SomeMethod(IHubContext<MyHub> context)
{
string result = await context.Clients.Client(connectionID).InvokeAsync<string>(
"GetMessage");
return result;
}
//---------------------------//
//Or by calling Client(...) or Caller on the Clients property in a Hub method
public class ChatHub : Hub
{
public async Task<string> WaitForMessage(string connectionId)
{
var message = await Clients.Client(connectionId).InvokeAsync<string>(
"GetMessage");
return message;
}
}
Using the following form with Invoke waits for and returns the response directly (just like a "real" synchronous method call)
var persons = hubProxy.Invoke<IEnumerable<Person>>("GetPersonsSynchronous", SearchCriteria, noteFields).Result;
foreach (Person person in persons)
{
Console.WriteLine($"{person.LastName}, {person.FirstName}");
}

Integration Test to publish to a topic and subscribe to another in Azure Service Bus is unreliable is there a race condition?

I am trying to write an integration / acceptance test to test some code in azure, the code in the question ATM simply subscribes to one topic and publishes to another.
I have written the test but it doesn't always pass, seems as though there could be a race condition in place. I've tried writing it a couple of ways including using OnMessage and also using Receive (example I show here).
When using OnMessage the test seemed to always exit prematurely (around 30 seconds), which I guess perhaps means its inappropriate for this test anyway.
My query concerning my example specifically, I assumed that once I created the subscription to the target topic, that any message sent to it I would be able to pickup using Receive(), whatever point in time that message arrived meaning, if the message arrives at the target topic before I call Receive(), I would still be able to read the message afterward by calling Receive(). Could anyone please shed any light on this?
namespace somenamespace {
[TestClass]
public class SampleTopicTest
{
private static TopicClient topicClient;
private static SubscriptionClient subClientKoEligible;
private static SubscriptionClient subClientKoIneligible;
private static OnMessageOptions options;
public const string TEST_MESSAGE_SUB = "TestMessageSub";
private static NamespaceManager namespaceManager;
private static string topicFleKoEligible;
private static string topicFleKoIneligible;
private BrokeredMessage message;
[ClassInitialize]
public static void BeforeClass(TestContext testContext)
{
//client for publishing messages
string connectionString = ConfigurationManager.AppSettings["ServiceBusConnectionString"];
string topicDataReady = ConfigurationManager.AppSettings["DataReadyTopicName"];
topicClient = TopicClient.CreateFromConnectionString(connectionString, topicDataReady);
topicFleKoEligible = ConfigurationManager.AppSettings["KnockOutEligibleTopicName"];
topicFleKoIneligible = ConfigurationManager.AppSettings["KnockOutIneligibleTopicName"];
//create test subscription to receive messages
namespaceManager = NamespaceManager.CreateFromConnectionString(connectionString);
if (!namespaceManager.SubscriptionExists(topicFleKoEligible, TEST_MESSAGE_SUB))
{
namespaceManager.CreateSubscription(topicFleKoEligible, TEST_MESSAGE_SUB);
}
if (!namespaceManager.SubscriptionExists(topicFleKoIneligible, TEST_MESSAGE_SUB))
{
namespaceManager.CreateSubscription(topicFleKoIneligible, TEST_MESSAGE_SUB);
}
//subscriber client koeligible
subClientKoEligible = SubscriptionClient.CreateFromConnectionString(connectionString, topicFleKoEligible, TEST_MESSAGE_SUB);
subClientKoIneligible = SubscriptionClient.CreateFromConnectionString(connectionString, topicFleKoIneligible, TEST_MESSAGE_SUB);
options = new OnMessageOptions()
{
AutoComplete = false,
AutoRenewTimeout = TimeSpan.FromMinutes(1),
};
}
[TestMethod]
public void E2EPOCTopicTestLT50()
{
Random rnd = new Random();
string customerId = rnd.Next(1, 49).ToString();
FurtherLendingCustomer sentCustomer = new FurtherLendingCustomer { CustomerId = customerId };
BrokeredMessage sentMessage = new BrokeredMessage(sentCustomer.ToJson());
sentMessage.CorrelationId = Guid.NewGuid().ToString();
string messageId = sentMessage.MessageId;
topicClient.Send(sentMessage);
Boolean messageRead = false;
//wait for message to arrive on the ko eligible queue
while((message = subClientKoEligible.Receive(TimeSpan.FromMinutes(2))) != null){
//read message
string messageString = message.GetBody<String>();
//Serialize
FurtherLendingCustomer receivedCustomer = JsonConvert.DeserializeObject<FurtherLendingCustomer>(messageString.Substring(messageString.IndexOf("{")));
//assertion
Assert.AreEqual(sentCustomer.CustomerId, receivedCustomer.CustomerId,"verify customer id");
//pop message
message.Complete();
messageRead = true;
//leave loop after processing one message
break;
}
if (!messageRead)
Assert.Fail("Didn't receive any message after 2 mins");
}
}
}
As the official document states about SubscriptionClient.Receive(TimeSpan):
Parameters
serverWaitTime
TimeSpan
The time span the server waits for receiving a message before it times out.
A Null can be return by this API if operation exceeded the timeout specified, or the operations succeeded but there are no more messages to be received.
Per my test, if a message sent to the topic and then delivered to your subscription within your specific serverWaitTime, then you could receive a message no matter whether the message arrives at the target topic before or after you call Receive.
When using OnMessage the test seemed to always exit prematurely (around 30 seconds), which I guess perhaps means its inappropriate for this test anyway.
[TestMethod]
public void ReceiveMessages()
{
subClient.OnMessage(msg => {
System.Diagnostics.Trace.TraceInformation($"{DateTime.Now}:{msg.GetBody<string>()}");
msg.Complete();
});
Task.Delay(TimeSpan.FromMinutes(5)).Wait();
}
For Subscription​Client.​On​Message, I assumed that it basically a loop invoking Receive. After calling OnMessage, you need to wait for a while and stop this method to exit. Here is a blog about the Event-Driven message programming for windows Azure Service Bus, you could refer to here.
Additionally, I found that your topicClient for sending messages and the subClientKoEligible for receiving a message are not targeted at the same topic path.

Azure Service Bus SubscriptionClient.OnMessage always completes message when it shouldnt

I am trying to receive all messages for a given subscription to a Service Bus Topic, but for the context of this app I do not want them dead lettered at this time, I just want to view them and leave them on the subscription. Despite instantiating the Client as
SubscriptionClient sc = SubscriptionClient.CreateFromConnectionString(connectionString, sub.topicName, sub.subscriptionName, ReceiveMode.PeekLock);
and making sure that I am using message.Abandon() rather than message.Complete() the message always gets Dead-lettered after accessing the message. I also have options.AutoComplete set to false
full method code below:
public List<ServiceBusMessage> RetrieveSubscriptionMessages(Subscription sub) {
ServiceBusMessage sbm;
List<ServiceBusMessage> list = new List<ServiceBusMessage>();
String connectionString = ConfigurationManager.AppSettings["Microsoft.ServiceBus.ConnectionString"].ToString();
SubscriptionClient sc = SubscriptionClient.CreateFromConnectionString(connectionString, sub.topicName, sub.subscriptionName, ReceiveMode.PeekLock);
OnMessageOptions options = new OnMessageOptions();
options.AutoComplete = false;
sc.OnMessage((message) => {
try {
sbm = new ServiceBusMessage() {
topicName = sub.topicName,
messageText = message.GetBody<String>()
};
list.Add(sbm);
message.Abandon();
}
catch (Exception) {
message.Abandon();
throw;
}
}, options);
return list;
}
Am I missing something ? Or is there an issue with auto dead-lettering with the onMessage() method?
Thanks !
When a message is abandoned the service bus will immediately make it available for re-delivery to any subscriber of the topic.
If you are trying to configure a multicast mechanism in which multiple listeners all receive the same message, then understand that all listeners on a given subscription will be competing for the same message. In order for every listener to receive its own copy of the message, then simply create a unique subscription to the topic for each listener.
If your intent is to delay re-delivery of the abandoned message, you might look at the SO question: What's the proper way to abandon an Azure SB Message so that it becomes visible again in the future in a way I can control?

SignalR chat room with Acknowledgement of message received after saving it to Sql data Base

Hello every one I am new to signalR I need little help to follow right approach in my chat module .
My Refernce :
http://www.codeproject.com/Articles/562023/Asp-Net-SignalR-Chat-Room but this article does not uses dataBase.
Steps which I have used to build my chat module are :
1 - Created chatHub class inherited from Hub class.
2 - On Connect(string enryptedId) function in chatHub class I am adding to List<UserDetail>.
3 - On SendPrivateMessage(string toData) I am saving it to database if it saved successfully to dataBase without any exception then sending it to both sender and reciever and binding on their communication messages <div>.
Problem in this approach - If after saving it to database if sender got disconnected due to network problem then sender will not recieve message from chathub class to client function so message is not appended to <div> which shows user communications but actually message sent successfully. Please can any one tell me the right approach to do this.
I would do it something like this, keep in mind its Pseduo code not tested or anything
private Dictionary<Guid, TaskCompletionSource<bool>> transactions = new Dictionary<Guid, TaskCompletionSource<bool>>();
public Task SendPrivateMessage(string content)
{
var taskCompletion = new TaskCompletionSource<bool>();
var transactionId = Guid.NewGuid();
transactions[transactionId] = taskCompletion;
var message = new Message
{
TransactionId = transactionId,
Content = content
};
GlobalHost
.ConnectionManager
.GetHubContext<MyHub>()
.Clients
.Client(connectionId)
.OnMessage(message);
return taskCompletion.Task;
}
public void OnTransactionConfirmed(Guid transactionId)
{
var taskCompletion = transactions[transactionId];
transactions.Remove(transactionId);
taskCompletion.SetResult(true);
}
Message is just a DTO
public class Message
{
public Guid TransactionId { get; set; }
public string Content { get; set; }
}
Basically you send a message to the client using the client method called OnMessage then you wait asynchronously until client confirms with the Hub method called OnTransactionConfirmed
Using a task makes it easier to work asynchronous in a synchronous way, the consumer of SendPrivateMessage could be asynchronous itself (If its called from a WebApi method or similar).
public Task MyWebApiMethod()
{
return myChatLogic.SendPrivateMessage("foo");
}
Or in a synchronous manner
public void MySyncMethod()
{
myChatLogic.SendPrivateMessage("foo").Wait(); //Thread will wait here until client asnwers
}
Note
To make this code failsafe you need to timeout the wait for confirmation and complete the task and remove the transactionId etc
your send message, receive message and display message should be handled separately. you did well with the send message except that you need to add a datetime stamp generated by the Server (do not use time from client) to each message and store it in sql. whenever there is a new message received, the Server should send this message to all online senders' and receivers' client browser or app.
here I said senderS because user A may login using a phone and at the same time login using office desktop browser, if user A send a message to user B mobile phone using the desktop browser, this message should be appear in 3 client device: user A desktop browser, user A mobile phone and user B mobile phone.
for the receiver, whenever go from offline to online, check the last message datetime stored in client side using cookie or file or clientside sqlite or clientside internal storage, with the Server database and get all related messages since last offline by comparing the last message datetime from clientside and message dt from server database.
for the display message, only read from client side storage so that the apps can run offline.
When sending a message, append a random string of 8 characters to the start of the message. On your message received function, cut out the first 8 characters and send those back to the original sender.
If you receive the same characters back from the receiver, then you know that receiver has received the message and has not been disconnected.
You can use this method to generate a random string.
private static IEnumerable<string> RandomStrings(
string allowedChars,
int minLength,
int maxLength,
Random rng)
{
char[] chars = new char[maxLength];
int setLength = allowedChars.Length;
int length = rng.Next(minLength, maxLength + 1);
for (int i = 0; i < length; ++i)
{
chars[i] = allowedChars[rng.Next(setLength)];
}
return new string(chars, 0, length);
}
you will can call the method like this
private string GetRandomStrings(){
const string AllowedChars =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz##$^*()";
Random rng = new Random();
return RandomStrings(AllowedChars, 8, 8, 1, rng)
}

Categories