How to save accesstoken to string from API (C# Xamarin Studio) - c#

I just started learning programming and I am currently trying out developing an android mobile application. I have watched a lot of tutorials and trying to find solutions online, but i can’t seem to get a hang of it.
So i am now reaching out to you guys for some help.
I am working in Xamarin Studio, C#, and I am trying to create a loginsystem with the help of a REST API, and i am not really sure how i save the accesstoken to a string so that i can use it in further requests.
FILE: MainActivity.cs
namespace APItest

{
[Activity(Label = "APItest", MainLauncher = true)]
public class MainActivity : Activity

{

private WebClient mClient;
private Uri mUrl;
private List<string> mItems;
private ListView mListView;


` public string acesstoken;`
` protected override void OnCreate(Bundle savedInstanceState)`

` {`
` base.OnCreate(savedInstanceState);`


SetContentView(Resource.Layout.Main);
mListView = FindViewById<ListView>(Resource.Id.myListView);

` mClient = new WebClient();`
` mUrl = new Uri("http://...link.../login");`
` mClient.DownloadDataAsync(mUrl);`


mClient.Headers.Add("User", ”email#gmail.com");
mClient.Headers.Add("Pass", ”myPassword”;
mClient.Headers.Add("Content-Type", "application/json");

mClient.Headers.Add("Accept", "application/json");


accesstoken = mClient.Headers.Get(”Accesstoken").ToString();


` mItems = new List<string>();`
` mItems.Add(accesstoken);`


 ArrayAdapter adapter = new ArrayAdapter(this, Android.Resource.Layout.SimpleListItem1, mItems);

``

mListView.Adapter = adapter;


 }

}

`}
In Postman API tool
(I send username and password in headers and receive following text)
{
"Result": {
”mRegister": {
"allowed": 0,
”Testmsg": "**"
},
` …`
"token": ”EB9TEBINlVOASM0Ok04RlIjI8JGMVNVV1smFu5MT"
}
}
I know i have a lot to learn, but i would really appreciate your help so i can get started.
Thank you in advance!


Have you tried:
accesstoken = mClient.Headers.Get("token").ToString();

Getting token form json string.
Using Newtonsoft.Json library, create your response Json object. And get the token string.
Following example show how to create a User json object to get the username and password:
using Newtonsoft.Json;
public class MainActivity : Activity
{
protected override async void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
BlobCache.ApplicationName = "AkavacheText";
string json = #"{ 'Username': 'Mike','Password': 'Ma'}";
SetContentView(Resource.Layout.Main);
var getData = JsonConvert.DeserializeObject<User>(json);
System.Console.WriteLine(getData.Username+"---"+ getData.Password);
}
}
public class User
{
public string Username { get; set; }
public string Password { get; set; }
}
string json should be your response string. User object should be your response object that structure depends on your json format.

Related

C# .NET Core 3.1 Web API Post parameter is Null

I am trying to make a post request from WPF to Web API using the following code but the request parameter is always null.
Request Model
public class Document
{
public string FileName { get; set; }
public byte[] Buffer { get; set; }
}
public class Request
{
public string Uploader { get; set; }
public List<Document> Documents { get; set; }
}
WPF Client
var obj = new Request()
{
Uploader = "John Doe",
Documents = new List<Document>
{
new Document()
{
FileName ="I Love Coding.pdf",
Buffer = System.IO.File.ReadAllBytes(#"C:\Users\john.doe\Downloads\I Love Coding.pdf.pdf")
}
}
};
using (var http = new HttpClient())
{
var encodedJson = JsonConvert.SerializeObject(obj);
var conent = new StringContent(encodedJson, Encoding.UTF8, "application/json");
HttpResponseMessage response = await http.PostAsync("https://my-app.com/api/upload", conent);
response.EnsureSuccessStatusCode();
}
Web API
[Route("")]
public class AppController : ControllerBase
{
[HttpPost]
[Route("api/upload")]
public async Task<IActionResult> UploadDocumentsAsync([FromBody] Request request)
{
// request is always null when app is running in production
// https://my-app.com/api/upload
//request is not null when running on https://localhost:8080/api/upload
}
}
Please what am I missing in the above implementation?
The request parameter is not null on localhost but always null in production.
Please what am I missing in the above implementation? The request
parameter is not null on localhost but always null in production.
Well, not sure how are getting data on local server becuse, you are sending MultipartFormData means your POCO object and file buffer. As you may know we can send json object in FromBody but not the files as json. Thus, I am not sure how it working in local and getting null data is logical in IIS Or Azure.
what am I missing in the above implementation?
As explained above, for sending both POCO object and Files as byte or steam we need to use FromForm and beside that, we need to bind our request object as MultipartFormDataContent to resolve your null data on your UploadDocumentsAsync API action.
Required Change For Solution:
WPF:
In your WPF http request please update your request code snippet as following:
var obj = new Request()
{
Uploader = "John Doe",
Documents = new List<Document>
{
new Document()
{
FileName ="I Love Coding.pdf",
Buffer = System.IO.File.ReadAllBytes(#"YourFilePath")
}
}
};
var httpClient = new HttpClient
{
BaseAddress = new("https://YourServerURL")
};
var formContent = new MultipartFormDataContent();
formContent.Add(new StringContent(obj.Uploader), "Uploader");
formContent.Add(new StringContent(obj.Documents[0].FileName), "Documents[0].FileName");
formContent.Add(new StreamContent(new MemoryStream(obj.Documents[0].Buffer)), "Documents[0].Buffer", obj.Documents[0].FileName);
var response = await httpClient.PostAsync("/api/upload", formContent);
if (response.IsSuccessStatusCode)
{
var responseFromAzureIIS = await response.Content.ReadAsStringAsync();
}
Note: Class in WPF side would remain same as before. No changes required.
Asp.net Core Web API:
In asp.net core web API side you should use [FromForm] instead of [FromBody]
So your controller Action would as following:
[Route("")]
public class AppController : ControllerBase
{
[HttpPost]
[Route("api/upload")]
public async Task<IActionResult> UploadDocumentsAsync([FromForm] Request file)
{
if (file.Documents[0].Buffer == null)
{
return Ok("Null File");
}
return Ok("File Received");
}
}
Note: For remote debugging I have checked the logs and for double check I have used a simple conditionals whether file.Documents[0].Buffer == null. I have tested both in local, IIS and Azure and working accordingly.
Update POCO Class in API Project:
For buffer you have used byte for your WPF project but for Web API project update that to IFormFile instead of byte. It should be as following:
public class Document
{
public string FileName { get; set; }
public IFormFile Buffer { get; set; }
}
public class Request
{
public string Uploader { get; set; }
public List<Document> Documents { get; set; }
}
Output:
If you would like to know more details on it you could check our official document here

SSL/TSL issue when attempting to post message from C# to Slack

I am attempting to post a message using web hooks through Slack in ASP.NET MVC C#. I am getting a SSL/TLS issue when attempting to execute. My code looks great, and I've compared it to several tutorials out there without finding any differences. Here is my SlackClient.cs :
public class SlackClient
{
private readonly Uri _uri;
private readonly Encoding _encoding = new UTF8Encoding();
public SlackClient(string urlWithAccessToken)
{
_uri = new Uri(urlWithAccessToken);
}
//Post a message using simple strings
public void PostMessage(string text, string username = null, string channel = null)
{
Payload payload = new Payload()
{
Channel = channel,
Username = username,
Text = text
};
PostMessage(payload);
}
public HttpResponseMessage PostMessage(Payload payload)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
string payloadJson = JsonConvert.SerializeObject(payload);
var content = new StringContent(payloadJson, Encoding.UTF8, "application/json");
using (HttpClient client = new HttpClient())
{ var result = client.PostAsync(_uri, content).Result; return result; }
}
}
//This class serializes into the Json payload required by Slack Incoming WebHooks
public class Payload
{
[JsonProperty("channel")]
public string Channel { get; set; }
[JsonProperty("username")]
public string Username { get; set; }
[JsonProperty("text")]
public string Text { get; set; }
}
And here is where I actually call the PostMessage (I've hidden by actual webhook address/token for security purposes)
public void SlackMessageTest()
{
string WebHookUrl = "https://myslackwebsite.slack.com/services/MYWEBHOOKURLFROMSLACK";
SlackClient client = new SlackClient(WebHookUrl);
client.PostMessage(username: "tester", text: "Testing Slack Integration!", channel: "#random");
}
The error I get is as follows:
The request was aborted: Could not create SSL/TLS secure channel.
So it seems I have an issue with my PostMessage method, with the URI return. From what I've researched, it should just work! My web hook is validated and set up correctly in Slack.
Any help is much appreciated!!
Slack requires TLS 1.2 and above
That said, replace SecurityProtocolType.Tls (TLS 1) with SecurityProtocolType.Tls12(TLS 1.2)
REF: SecurityProtocolType Enum
Hth.

How to convert Object and view it on WebAPI?

Here is my question,
I got a Web API and client(winform), client will send out data with a Serialize Object. My Web API do have received and return a response to client. But I can't view the data on Web API, I do have try using Deserialize Object and convert it into string but not working neither.
Please help me,Thanks!
Here is my code:
Client
private string WebApiPost(string sParam, string sJson)
{
var client = new HttpClient();
var content = new StringContent(sJson, Encoding.UTF8, "application/json");
var response = client.PostAsync(sWebAPI_URL + sParam, content).Result;
var body = response.Content.ReadAsStringAsync().Result;
return body;
}
This is my Web API
public object Post([FromBody]object hL7)
{
//what should I do???
//I've tried set hL7 into string but it wont get any data;
//I've also tried deserialize it but will get 500 internal server error.
return hL7;
}
This is my WebAPI model
public class HL7MID
{
public string LOC { get; set; }
public string COMPANY { get; set; }
}
public class HL7MID_List
{
public string sMSG { get; set; }
public List<HL7MID> data = new List<HL7MID>();
}
Because sJson matches HL7MID, you can use that type in as a paramter of your Post function, and just use that type.
public HL7MID Post([FromBody]HL7MID hL7)
{
//use hL7 here
return hL7;//also since you know the return type, changing that to HL7MID is suggested
}

How to Enable/Disable Azure Function programmatically

Is there a way to programmatically enable/disable an Azure function?
I can enable/disable a function using the portal under the "Manage" section, which causes a request to be sent to https://<myfunctionapp>.scm.azurewebsites.net/api/functions/<myfunction>
The JSON payload looks a bit like:
{
"name":"SystemEventFunction",
"config":{
"disabled":true,
"bindings":[
// the bindings for this function
]
}
// lots of other properties (mostly URIs)
}
I'm creating a management tool outside of the portal that will allow users to enable and disable functions.
Hoping I can avoid creating the JSON payload by hand, so I'm wondering if there is something in an SDK (WebJobs??) that has this functionality.
Further to #James Z.'s answer, I've created the following class in C# that allows you to programmatically disable / enable an Azure function.
The functionsSiteRoot constructor argument is the Kudu root of your Functions application, eg https://your-functions-web-app.scm.azurewebsites.net/api/vfs/site/wwwroot/
The username and password can be obtained from "Get publish profile" in the App Service settings for your Functions.
public class FunctionsHelper : IFunctionsHelper
{
private readonly string _username;
private readonly string _password;
private readonly string _functionsSiteRoot;
private WebClient _webClient;
public FunctionsHelper(string username, string password, string functionsSiteRoot)
{
_username = username;
_password = password;
_functionsSiteRoot = functionsSiteRoot;
_webClient = new WebClient
{
Headers = { ["ContentType"] = "application/json" },
Credentials = new NetworkCredential(username, password),
BaseAddress = functionsSiteRoot
};
}
public void StopFunction(string functionName)
{
SetFunctionState(functionName, isDisabled: true);
}
public void StartFunction(string functionName)
{
SetFunctionState(functionName, isDisabled: false);
}
private void SetFunctionState(string functionName, bool isDisabled)
{
var functionJson =
JsonConvert.DeserializeObject<FunctionSettings>(_webClient.DownloadString(GetFunctionJsonUrl(functionName)));
functionJson.disabled = isDisabled;
_webClient.Headers["If-Match"] = "*";
_webClient.UploadString(GetFunctionJsonUrl(functionName), "PUT", JsonConvert.SerializeObject(functionJson));
}
private static string GetFunctionJsonUrl(string functionName)
{
return $"{functionName}/function.json";
}
}
internal class FunctionSettings
{
public bool disabled { get; set; }
public List<Binding> bindings { get; set; }
}
internal class Binding
{
public string name { get; set; }
public string type { get; set; }
public string direction { get; set; }
public string queueName { get; set; }
public string connection { get; set; }
public string accessRights { get; set; }
}
No, this is not possible currently. The disabled metadata property in function.json is what determines whether a function is enabled. The portal just updates that value when you enable/disable in the portal.
Not sure if it will meet your needs, but I'll point out that there is also a host.json functions array that can be used to control the set of functions that will be loaded (documented here). So for example, if you only wanted 2 of your 10 functions enabled, you could set this property to an array containing only those 2 function names (e.g. "functions": [ "QueueProcessor", "GitHubWebHook" ]), and only those will be loaded/enabled. However, this is slightly different than enable/disable in that you won't be able to invoke the excluded functions via the portal, whereas you can portal invoke disabled functions.
Further to #DavidGouge 's answer above, the code he posted does work, I just tested it and will be using it in my app. However it needs a couple of tweaks:
Remove the inheritance from IFunctionsHelper. I'm not sure what that interface is but it wasn't required.
Change the class definition for Binding as follows:
internal class Binding
{
public string name { get; set; }
public string type { get; set; }
public string direction { get; set; }
public string queueName { get; set; }
public string connection { get; set; }
public string accessRights { get; set; }
public string schedule { get; set; }
}
After that it would work.
P.S. I would have put this as a comment on the original answer, but I don't have enough reputation on Stack Overflow to post comments!
Using a combination of #Satya V's and #DavidGouge's solutions, I came up with this:
public class FunctionsHelper
{
private readonly ClientSecretCredential _tokenCredential;
private readonly HttpClient _httpClient;
public FunctionsHelper(string tenantId, string clientId, string clientSecret, string subscriptionId, string resourceGroup, string functionAppName)
{
var baseUrl =
$"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{functionAppName}/";
var httpClient = new HttpClient
{
BaseAddress = new Uri(baseUrl)
};
_httpClient = httpClient;
_tokenCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
}
private async Task SetAuthHeader()
{
var accessToken = await GetAccessToken();
_httpClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse($"Bearer {accessToken}");
}
private async Task<string> GetAccessToken()
{
return (await _tokenCredential.GetTokenAsync(
new TokenRequestContext(new[] {"https://management.azure.com/.default"}))).Token;
}
public async Task StopFunction(string functionName)
{
await SetFunctionState(functionName, isDisabled: true);
}
public async Task StartFunction(string functionName)
{
await SetFunctionState(functionName, isDisabled: false);
}
private async Task SetFunctionState(string functionName, bool isDisabled)
{
await SetAuthHeader();
var appSettings = await GetAppSettings();
appSettings.properties[$"AzureWebJobs.{functionName}.Disabled"] = isDisabled ? "1" : "0";
var payloadJson = JsonConvert.SerializeObject(new
{
kind = "<class 'str'>", appSettings.properties
});
var stringContent = new StringContent(payloadJson, Encoding.UTF8, "application/json");
await _httpClient.PutAsync("config/appsettings?api-version=2019-08-01", stringContent);
}
private async Task<AppSettings> GetAppSettings()
{
var res = await _httpClient.PostAsync("config/appsettings/list?api-version=2019-08-01", null);
var content = await res.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<AppSettings>(content);
}
}
internal class AppSettings
{
public Dictionary<string, string> properties { get; set; }
}
The problem with using the Kudu api to update the function.json file is that it will be overwritten on any subsequent deploy. This uses Azure's Rest Api to update the Configuration of the application. You will first need an Azure Service Principle to use the api though.
Using the Azure Cli, you can run az ad sp create-for-rbac to generate the Service Principle and get the client id and client secret. Because the UpdateConfiguration endpoint does not allow you to update a single value, and overwrites the entire Configuration object with the new values, you must first get all the current Configuration values, update the one you want, and then call the Update endpoint with the new Configuration keys and values.
I would imagine you can use Kudu REST API (specifically VFS) to update the disabled metadata property in function.json. Would that disable the function?
Here is the Kudu REST API. https://github.com/projectkudu/kudu/wiki/REST-API
The CLI command That is used to disable the Azure function through CLI - documented here
az functionapp config appsettings set --name <myFunctionApp> \
--resource-group <myResourceGroup> \
--settings AzureWebJobs.QueueTrigger.Disabled=true
I had captured fiddler while while running the above command.
Azure CLI works on the Python process The python process was issuing request to
https://management.azure.com to update appsetting.
got a reference to the same endpoint in the below REST Endpoint :
https://learn.microsoft.com/en-us/rest/api/appservice/webapps/updateapplicationsettings
Request URI :
PUT
https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/config/appsettings?api-version=2019-08-01
Headers :
Authorization: Bearer <> ;
Content-Type: application/json; charset=utf-8
Request Body:
{"kind": "<class 'str'>", "properties":JSON}
We can hardcode the properties or get it dynamically. For disabling the function, will have to update the JSON node of Properties : Azure.WebJobs.QueueTrigger.Disabled = True
To get properties you could use the endpoint, you could refer Web Apps - List Application Settings
The Output looks up as below :
Hope this helps :)
What about this: https://learn.microsoft.com/en-us/azure/azure-functions/disable-function?tabs=portal#localsettingsjson
This looks like the easiest solution for local development.

Adobe Sign (echo sign) API sending document using C#

Okay I have limited understanding of working with API's
Im trying to get to grips with Adobe Sign API and hit a dead end, on there test page i have enterd this and it works
But i have no idea on how then do that in C#
I have tried the following, but know its missing the OAuth stuff and I'm just not sure what to try next.
by the way foo.GetAgreementCreationInfo() just gets the string that is in the screen shot, I just moved it out cus it was big and ugly
var foo = new Models();
var client = new RestClient("https://api.na1.echosign.com/api/rest/v5");
// client.Authenticator = new HttpBasicAuthenticator(username, password);
var request = new RestRequest("agreements/{AgreementCreationInfo}", Method.POST);
request.AddParameter("name", "value"); // adds to POST or URL querystring based on Method
request.AddUrlSegment("AgreementCreationInfo", foo.GetAgreementCreationInfo()); // replaces matching token in request.Resource
IRestResponse response = client.Execute(request);
var content = response.Content; // raw content as string
You are misinterpreting the API documentation. The Access-Token parameter needed in your API is clearly an HTTP header, while the AgreementCreationInfo is simply the request body in JSON format. There is no URI segment, so rewrite your code as follows:
var foo = new Models();
//populate foo
var client = new RestClient("https://api.na1.echosign.com/api/rest/v5");
var request = new RestRequest("agreements", Method.POST);
request.AddHeader("Access-Token", "access_token_here!");
// request.AddHeader("x-api-user", "userid:jondoe"); //if you want to add the second header
request.AddParameter("application/json", foo.GetAgreementCreationInfo(), ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
var content = response.Content;
Please also be aware that in RESTSharp you do not need to manually serialize your body into JSON at all. If you create a strongly typed object (or just an anonymous object could be enough) that has the same structure of your final JSON, RESTSharp will serialize it for you.
For a better approach I strongly suggest you to replace this line:
request.AddParameter("application/json", foo.GetAgreementCreationInfo(), ParameterType.RequestBody);
With those:
request.RequestFormat = DataFormat.Json;
request.AddBody(foo);
Assuming your foo object is of type Models and has the following structure along with its properties:
public class Models
{
public DocumentCreationInfo documentCreationInfo { get; set; }
}
public class DocumentCreationInfo
{
public List<FileInfo> fileInfos { get; set; }
public string name { get; set; }
public List<RecipientSetInfo> recipientSetInfos { get; set; }
public string signatureType { get; set; }
public string signatureFlow { get; set; }
}
public class FileInfo
{
public string transientDocumentId { get; set; }
}
public class RecipientSetInfo
{
public List<RecipientSetMemberInfo> recipientSetMemberInfos { get; set; }
public string recipientSetRole { get; set; }
}
public class RecipientSetMemberInfo
{
public string email { get; set; }
public string fax { get; set; }
}
Link to AdobeSign Repository:
ADOBE SIGN SDK C# SHARP API Ver. 6
Adobe Sign API integrators - this is kind of hidden away in AdobeSigns GIT repositories. The link to all the generated SWAGGER classes (models/methods) for C# and REST client integrated C# project in a GIT project you can compile and use right inside your project as a project reference or compiled DLL. This project has been updated to use version 6 of the API. This was a huge time saver for me. I have provided a quick example below on how to use it. I hope this helps others save time as well.
Note you might have to switch out BasePath in the configuration.cs so you can retrieve the initial Adobe URI "BaseURI" call if you get 404 error.
Change BasePath = "http://localhost/api/rest/v6";
To:
BasePath = "https://api.echosign.com/api/rest/v6";
//include namespaces:
using IO.Swagger.Api;
using IO.Swagger.model.agreements;
using IO.Swagger.model.baseUris;
using IO.Swagger.model.transientDocuments;
using System.IO;
Then this quick minimal demonstrates BaseUri, Upload PDF a.k.a. Transient Document, then Create Agreement (Example 1 Basic Signer Minimal Options)
string transientDocumentId = "";
string adobesignDocKey = "";
string baseURI = "";
var apiInstanceBase = new BaseUrisApi();
var authorization = "Bearer " + apiKey; //Example as Integration Key, see adobesign docs For OAuth.
try
{
//___________________GET BASEURI ADOBE SIGN_________________________
BaseUriInfo resultBase = apiInstanceBase.GetBaseUris(authorization);
baseURI = resultBase.ApiAccessPoint; //return base uri
//___________________UPLOAD YOUR PDF THEN REF ADOBE SIGN_________________________
var apiInstanceFileUpload = new TransientDocumentsApi(baseURI + "api/rest/v6/");
TransientDocumentResponse resultTransientID = apiInstanceFileUpload.CreateTransientDocument(authorization, File.OpenRead([ENTER YOUR LOCAL FILE PATH]), null, null, _filename, null);
if (!String.IsNullOrEmpty(resultTransientID.TransientDocumentId))
{
transientDocumentId = resultTransientID.TransientDocumentId; //returns the transient doc id to use below as reference
}
var apiInstance = new AgreementsApi(baseURI + "api/rest/v6/");
//___________________CREATE ADOBE SIGN_________________________
var agreementId = ""; // string | The agreement identifier, as returned by the agreement creation API or retrieved from the API to fetch agreements.
var agreementInfo = new AgreementCreationInfo();
//transientDocument, libraryDocument or a URL (note the full namespace/conflicts with System.IO
List<IO.Swagger.model.agreements.FileInfo> useFile = new List<IO.Swagger.model.agreements.FileInfo>();
useFile.Add(new IO.Swagger.model.agreements.FileInfo { TransientDocumentId = transientDocumentId });
agreementInfo.FileInfos = useFile;
//Add Email To Send To:
List<ParticipantSetMemberInfo> partSigners = new List<ParticipantSetMemberInfo>();
partSigners.Add( new ParticipantSetMemberInfo { Email = "[ENTER VALID EMAIL SIGNER]", SecurityOption=null });
//Add Signer To Participant
List<ParticipantSetInfo> partSetInfo = new List<ParticipantSetInfo>();
partSetInfo.Add(new ParticipantSetInfo { Name = "signer1", MemberInfos = partSigners, Role = ParticipantSetInfo.RoleEnum.SIGNER, Order=1, Label="" });
agreementInfo.ParticipantSetsInfo = partSetInfo;
agreementInfo.SignatureType = AgreementCreationInfo.SignatureTypeEnum.ESIGN;
agreementInfo.Name = "Example Esign For API";
agreementInfo.Message = "Some sample Message To Use Signing";
agreementInfo.State = AgreementCreationInfo.StateEnum.INPROCESS;
AgreementCreationResponse result = apiInstance.CreateAgreement(authorization, agreementInfo, null, null);
adobesignDocKey = result.Id; //returns the document Id to reference later to get status/info on GET
}
catch (Exception ex)
{
//Capture and write errors to debug or display to user
System.Diagnostics.Debug.Write(ex.Message.ToString());
}

Categories