Twilio SMS messaging not working in Console Application - c#

I funded my Twilio account and am working in a console application. When to go to the documentation (Here: https://www.twilio.com/user/account/developer-tools/api-explorer/message-create) and enter my phone number the request works. However, when I copy the code to a local console application nothing happens. I literally copy the code line for line and make sure the SID, Token, and Numbers are correct and nothing happens at all, the console app just runs to the end of execution.
string AccountSid = "MySID";
string AuthToken = "MyAuthToken";
var twilio = new TwilioRestClient(AccountSid, AuthToken);
var message = twilio.SendSmsMessage("+12222222222", "+13333333333","Hello World");
Console.WriteLine(message.Sid);
I run Fiddler and I get this for the Raw Packet. Also Fiddler says the result is a 401 status code.
POST https://api.twilio.com/2010-04-01/Accounts/MySID/SMS/Messages.json HTTP/1.1
Authorization: Basic {TonsOfRandomCharactersThatLookLikeISHouldHide}
Accept: application/json, application/xml, text/json, text/x-json, text/javascript, text/xml
Accept-charset: utf-8
User-Agent: twilio-csharp/3.4.1.0 (.NET 4.0.30319.17929)
Content-Type: application/x-www-form-urlencoded
Host: api.twilio.com
Content-Length: 56
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
From=%2B14697891380&To=%2B12146630105&Body=New%20Message
Any ideas on what could be going one? I know others are having this issue, I see it posted in other places, but I have yet to see a response.
Also here is a link to another person having this issue. I would comment, but I do not have the reputation to enter a comment, hence why I made another thread (Why is Twilio not sending sms?)

I was unable to get Twilio to work, this is not the answer to the technical issues (I think for some reason Twilio just has not authorized my account), but for those of you prototyping and need something asap I ended up using Plive and has a call and text message working within an hour. Here is my Sample code and it is actually cheaper than Twilio. I really like Twilio and have used it in the past, but never with C#. So maybe I can still get the issue resolved asap.
using System;
using System.Collections.Generic;
using System.Reflection;
using RestSharp;
using Plivo.API;
namespace Plivo2
{
class Program
{
static void Main(string[] args)
{
string auth_id = "MyAuthID"; // obtained from Plivo account dashboard
string auth_token = "MyAuthTokey"; // obtained from Plivo account dashboard
// Making a Call
string from_number = "MyPliveNumber";
string to_number = "TheNumberYouWantToContact";
SendMessage(auth_id, auth_token, from_number, to_number,"Hello World!");
}
private static void CallPhone(string auth_id,string auth_token, string fromNumber, string toNumber){
// Creating the Plivo Client
RestAPI plivo = new RestAPI(auth_id, auth_token);
IRestResponse<Call> response = plivo.make_call(new Dictionary<string, string>() {
{ "from", fromNumber },
{ "to", toNumber },
{ "answer_url", "http://some.domain.com/answer/" },
{ "answer_method", "GET" }
});
// The "Outbound call" API response has four properties -
// message, request_uuid, error, and api_id.
// error - contains the error response sent back from the server.
if (response.Data != null)
{
PropertyInfo[] proplist = response.Data.GetType().GetProperties();
foreach (PropertyInfo property in proplist)
Console.WriteLine("{0}: {1}", property.Name, property.GetValue(response.Data, null));
}
else
Console.WriteLine(response.ErrorMessage);
}
private static void SendMessage(string auth_id,string auth_token, string fromNumber, string toNumber, string message) {
RestAPI plivo = new RestAPI(auth_id, auth_token);
IRestResponse<MessageResponse> resp = plivo.send_message(new Dictionary<string, string>()
{
{ "src", fromNumber },
{ "dst", toNumber },
{ "text", message },
{ "url", "http://some.domain/receivestatus/" },
{ "method", "GET" }
});
if (resp.Data != null)
{
PropertyInfo[] proplist = resp.Data.GetType().GetProperties();
foreach (PropertyInfo property in proplist)
Console.WriteLine("{0}: {1}", property.Name, property.GetValue(resp.Data, null));
}
else
Console.WriteLine(resp.ErrorMessage);
}
}
}

Twilio evangelist here.
The code you posted looks correct to me, but based on the Fiddler output it sounds like your getting an authentication error so I would double check that you've copy and pasted your account sid and auth token from your Twilio account dashboard correctly.
Hope that helps.

Related

Why C# library for SengGrid API-v3 is on fictional email to SetFrom method get bad requests?

I am using Senddrid C# library provided by SendGrid to send messages. I have dynamic template. On user click it sends emails with dynamic fields.
So problem is what by some means I cannot use fictional email for that I must use real/activated email. Just in case inputs are valid or got from appseting.js and are also valid. Its problem with "SetFrom" method. No problems with template id or app_key
! Real email - I mean email what I could log-in and send messages for example
! Fake email - to use as stub. for example no-reply#gmail.com
private async Task mySendEmail(string toAddress, string templateId, JObject dynamicTemplateData, string? toWhome = null, string? setFromEmail = null, string? setFromName = null)
{
if (setFromEmail is null)
setFromEmail = sendGridConfig.SendGridApiFrom;
if (setFromName is null)
setFromName = sendGridConfig.SendGridApiFromDisplayName;
// var foo = sendGridConfig.SendGridApiKey;
var client = new SendGridClient(sendGridConfig.SendGridApiKey);
var msg = new SendGridMessage();
msg.AddTo(new EmailAddress(toAddress, toWhome));
//msg.SetFrom("my-real-email#gmail.com", setFromName); // if using real email it works
msg.SetFrom(new EmailAddress("test#example.com", "Example User 0")); // fictional email have bad requests
msg.SetTemplateData(dynamicTemplateData);
msg.SetTemplateId(templateId);
var response = await client.SendEmailAsync(msg); // Bad request
}
The output of the result
{"from":{"name":"Example User 0","email":"test#example.com"},"personalizations":[{"to":[{"name":"Stone Ocean","email":"count_zero#inbox.lv"}],"dynamic_template_data":{"url":"CUCUMBER#INBOX.LV","password":"TEREMOK"}}],"template_id":"d-2f393a3c5ca7451ea856fc1acadf0bd7"}
Forbidden
{"errors":[{"message":"The from address does not match a verified Sender Identity. Mail cannot be sent until this error is resolved. Visit https://sendgrid.com/docs/for-developers/sending-email/sender-identity/ to see the Sender Identity requirements","field":"from","help":null}]}
Connection: keep-alive
Access-Control-Allow-Origin: https://sendgrid.api-docs.io
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Authorization, Content-Type, On-behalf-of, x-sg-elas-acl
Access-Control-Max-Age: 600
X-No-CORS-Reason: https://sendgrid.com/docs/Classroom/Basics/API/cors.html
Strict-Transport-Security: max-age=600; includeSubDomains
Date: Thu, 24 Nov 2022 07:37:40 GMT
Server: nginx
Any ideas why SetFrom need real email not fictional and how to avoid it?
According SendGrid the sender email(email what is sent FROM) must be verified by SendGrid to send email from it. Otherwise request throws error. Why this click

NServicebus receive messages without all the NServicebus specific stuff

I am new to NServicebus and have struggled to find an answer in the documentation.
I am trying to receive a message that is posted to Amazon SQS in a simple JSON format like this:
"MyMessage": {
"Id": 1,
"Name": "Name",
"Field1": "text",
"Field2": 1,
"Field3": false
}
However whenever this gets sent to the queue my NServicebus subscriber says it is a poison message and doesn't try to handle it.
I realize that this message is missing a lot of NServicebus stuff because when I publish a message via NServicebus it looks like this:
{
"Headers": {
"NServiceBus.MessageId": "a244a014-e331-41e6-b6ca-aed6011af905",
"NServiceBus.MessageIntent": "Publish",
"NServiceBus.ConversationId": "e42f0308-4c51-4787-ade0-aed6011af90f",
"NServiceBus.CorrelationId": "a244a014-e331-41e6-b6ca-aed6011af905",
"NServiceBus.OriginatingMachine": "DESKTOP-1234567",
"NServiceBus.OriginatingEndpoint": "endpoint",
"$.diagnostics.originating.hostid": "da7dce712dfbc0f093aa30eb7f25d2b4",
"NServiceBus.ContentType": "application/json",
"NServiceBus.EnclosedMessageTypes": "Type",
"NServiceBus.Version": "7.7.3",
"NServiceBus.TimeSent": "2022-07-18 17:10:16:400164 Z"
},
"Body": "Base 64 encoded string here",
"S3BodyKey": null
}
The problem is the message I am receiving is not published via NServicebus and comes in the format I showed above. It doesn't have all of the headers and a body that is base64 encoded.
Is there a way to set up NServicebus to be able to receive and handle such a message? Or is it just not built to handle stuff like this?
Note: This is a .Net 6 application
Edit: I found this article that mentions how NServicebus can receive messages without all the headers, but it doesn't mention how.
https://www.bradjolicoeur.com/Article/nsb-features-message-headers
What you want is called Native Send and is actually documented. You have to conform your messages to the format NServiceBus expects in order to be able to have handlers correctly process it.
A native send function would look like this:
public static async Task SendMessage(IAmazonSQS sqsClient, string queue, string messageBody, Dictionary<string, string> headers)
{
var bodyBytes = Encoding.UTF8.GetBytes(messageBody);
var base64Body = Convert.ToBase64String(bodyBytes);
var serializedMessage = Newtonsoft.Json.JsonConvert.SerializeObject(new
{
Headers = headers,
Body = base64Body,
});
var queueUrlResponse = await sqsClient.GetQueueUrlAsync(QueueNameHelper.GetSqsQueueName(queue));
await sqsClient.SendMessageAsync(queueUrlResponse.QueueUrl, serializedMessage);
}
To use this you'd need to specify message type and some other header values:
await SendMessage(
sqsClient: client,
queue: "samples-sqs-nativeintegration",
messageBody: "{Property:'PropertyValue'}",
headers: new Dictionary<string, string>
{
{"NServiceBus.EnclosedMessageTypes", "MessageTypeToSend"},
{"NServiceBus.MessageId", "99C7320B-A645-4C74-95E8-857EAB98F4F9"}
}
);

Azure Maps Batch Geocoding Status 202 12+ Hours Later

I'm using RestSharp to programmatically build/make/deserialize a call to the Azure Maps API for batch geocoding. Currently testing the process with a 5 address batch--something I expected to go quickly after all the posts about "10k addresses in minutes." But a request I make successfully yesterday is still not available, only showing the "Accepted 202" status that the documentation says means it's still processing...and there's no outage showing at the status page.
I've replicated these calls and results using Postman, so I'm not sure there's a code problem per se...but it wouldn't be the first time I got tunnel vision and overlooked something obvious.
My POST call is generated with the code below, and returns an OK status with the necessary Location header with what looks like a valid link.
public RestRequest CreateBatchRequest()
{
var request = new RestRequest($"{_batchAddressEndpoint}subscription-key={_apiToken}", Method.POST);
request.AddQueryParameter("api-version", _version);
var batchRequestBody = GenerateQueryBatch();
var requestBodyJson = JsonConvert.SerializeObject(batchRequestBody);
request.AddHeader("Content-Type", "application/json");
request.AddParameter("undefined", requestBodyJson, ParameterType.RequestBody);
return request;
}
protected AzureBatchRequest GenerateQueryBatch()
{
var requestBody = new AzureBatchRequest();
foreach (var address in Addresses)
{
var addressString = $"{address.Address}, {address.City}, {address.State}";
if (!string.IsNullOrEmpty(_country))
addressString = $"{address.Address}, {address.City}, {address.State}, {_country.ToUpper()}";
requestBody.Queries.Add($"?query={addressString}&limit={_resultLimit}");
}
return requestBody;
}
This gives me a body parameter for the request that appears to match the documentation (actual addresses hidden for privacy reason, but they've been successfully geocoded with other services)...
{
undefined={"queries":[
"?query=123 MAIN ST, LOS ANGELES, CA&limit=3",
"?query=123 MAIN ST, PLEASANTVILLE, CA&limit=3",
"?query=123 MAIN ST, STOCKTON, CA&limit=3",
"?query=123 MAIN ST, SAN DIEGO, CA&limit=3",
"?query=123 MAIN ST, REDDING, CA&limit=3"
]}
}
I get the Location header value and make the GET call with it using the code below...
public List<Coordinate> DeserializeBatchResponse(RestResponse response)
{
var batchLink = response.Headers.Where(header => header.Name.Equals("Location")).FirstOrDefault();
var request = new RestRequest(batchLink.Value.ToString(), Method.GET);
var batch = SendRequest(request);
if (batch.StatusCode == System.Net.HttpStatusCode.Accepted)
{
var isProcessing = true;
while (isProcessing)
{
Thread.Sleep(TimeSpan.FromSeconds(60));
request = new RestRequest(batchLink.Value.ToString(), Method.GET);
batch = SendRequest(request);
if (batch.StatusCode != System.Net.HttpStatusCode.Accepted)
isProcessing = false;
}
}
}
And it never leaves that loop. When I hardcode the URL returned from yesterday's POST request, it has the same behavior--as ditto when tried in Postman to isolate from the rest of my code.
Does anyone have any insight?
UPDATE
We discovered that after creating a new plan at a higher tier (the S1 rather than S0 tier) there was no noticeable delay on the batch calls. Still not a solution, per se, because that prices us out of the product for production purposes, but possibly a fix for others until the updates mentioned in the accepted answer come to fruition.
This is to be expected at the moment while in preview. This will be moving out of preview soon and will be significantly faster.

how to impersonate a user via odata

We have been succesful in using the odata v8.1 endpoint in 2016 to impersonate a user.
Please note that the intended request flow is: Postman-->LocalHost Microservice-->CRM
Example of a working request from Postman-->CRM (directly, without going through the microservice)
Accept:application/json
Content-Type:application/json; charset=utf-8
OData-MaxVersion:4.0
OData-Version:4.0
MSCRMCallerID:d994d6ff-5531-e711-9422-00155dc0d345
Cache-Control:no-cache
Against the odata endpoint: ..../api/data/v8.1/leads
Note that this has been successful only when issued directly against the odata v8.1 endpoint via postman.
When attempting to do the same, having a service running locally (Postman-->LocalHost Service-->CRM), this fails, and simply ignores??? the MSCRMCallerID header.
Upon examining headers that were passed to the LocalHost Microservice from Postman, the request, as examined by the debugger in VS 2017:
{Method: POST, RequestUri: 'https://.../api/data/v8.1/leads', Version: 1.1, Content: System.Net.Http.StringContent, Headers:
{
OData-Version: 4.0
OData-MaxVersion: 4.0
MSCRMCallerID: D994D6FF-5531-E711-9422-00155DC0D345
Cache-Control: no-cache
Accept: application/json
Content-Type: application/json; charset=utf-8
}}
The record is created succesfully, however on the CreatedBy field is the service username NOT the MSCRMCallerID username (d994d6ff-5531-e711-9422-00155dc0d345), and the CreatedOnBehalf field is empty.
What are we doing wrong?
How do we get this impersonation working from our service?
EDIT + More Info
Please note that I do believe that I've included all the relevant info, but if I have not, please let me know what other input I should provide on this issue.
What have I tried?
changed the order of headers
played with the case of the headers
ensured that the guid is correct of the user for impersonation
ensured that the user has both delegate and sys admin role (although this is irrelevant because this works when executing requesting directly against crm odata endpoint, rather than the endpoint that the our service exposes
have tried to execute the request against both https AND http
fiddler trace as shown below
Please note that this fiddler trace is a trace showing Postman --> Microservice request. It does not show the communication from the localhost microservice to CRM. (I'm not sure why, perhaps because it is encrypted)
POST https://localhost:19081/.....Leads/API/leads HTTP/1.1
Host: localhost:19081
Connection: keep-alive
Content-Length: 84
Cache-Control: no-cache
Origin: chrome-extension://aicmkgpgakddgnaphhhpliifpcfhicfo
MSCRMCallerID: D994D6FF-5531-E711-9422-00155DC0D345
X-Postman-Interceptor-Id: d79b1d2e-2155-f2ec-4ad7-e9b63e7fb90d
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36
Content-Type: application/json; charset=UTF-8
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8
Cookie: ai_user=Ka2Xn|2017-05-25T17:30:57.941Z
{
"subject": "created by mscrmcaller user2: d994d6ff-5531-e711-9422-00155dc0d345"
}
#Ram has suggested that we use the organization service to authenticate, is this an option, considering we are executing against Web API? Will the requested token still be valid. (Please note that this may be a silly question, and the reason is because I am not understanding how authentication works).
The following is a code snippet from how we are authenticating currently on every call:
//check headers to see if we got a redirect to the new location
var shouldAuthenticate = redirectUri.AbsoluteUri.Contains("adfs/ls");
if (!shouldAuthenticate)
{
return;
}
var adfsServerName = redirectUri.Authority;
var queryParams = HttpUtility.ParseQueryString(redirectUri.Query);
ServicePointManager.ServerCertificateValidationCallback +=
(sender, cert, chain, sslPolicyErrors) => true;
WSTrustChannelFactory factory = null;
try
{
// use a UserName Trust Binding for username authentication
factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
$"https://{adfsServerName}/adfs/services/trust/13/usernamemixed")
{
Credentials =
{
UserName =
{
UserName = $"{credential.Domain}\\{credential.UserName}",
Password = credential.Password
}
},
TrustVersion = TrustVersion.WSTrust13
};
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
AppliesTo = new EndpointReference(_client.BaseAddress.AbsoluteUri),
TokenType = "urn:oasis:names:tc:SAML:1.0:assertion",
KeyType = KeyTypes.Bearer
};
var channel = factory.CreateChannel();
channel.Issue(rst, out RequestSecurityTokenResponse rstr);
var fedSerializer = new WSFederationSerializer();
var rstrContent = fedSerializer.GetResponseAsString(rstr, new WSTrustSerializationContext());
// construct a authentication form
var crmauthenticaionPostDictionary = new Dictionary<string, string>
{
{"wa", queryParams["wa"]},
{"wresult", rstrContent},
{"wctx", queryParams["wctx"]}
};
// post the authentication form to the website.
var crmAuthorizationPostResponse = _client.PostAsync(_client.BaseAddress.AbsoluteUri, new FormUrlEncodedContent(crmauthenticaionPostDictionary)).Result;
var crmAuthorizationPostResponseString = crmAuthorizationPostResponse.Content.ReadAsStringAsync().Result;
//we should be authenticated here
if (
!(
// we are correctly authorized if we got redirected to the correct address that we
// were trying to reach in the first place.
crmAuthorizationPostResponse.StatusCode == HttpStatusCode.Redirect
&& crmAuthorizationPostResponse.Headers.Location == authenticationTestUri
)
)
{
throw new Exception("ADFS Authentication to CRM failed.");
}
When you are doing Postman to CRM request, its direct call & CRM handles it in expected way.
But in Postman -> Microservice -> CRM, the header get lost between Microservice to CRM.
In your Microservice, you have to handle the Header forward manually to CRM SDK call.
HttpWebRequest myHttpWebRequest1= (HttpWebRequest)WebRequest.Create(uri);
myHttpWebRequest1.Headers.Add("MSCRMCallerID", "D994D6FF-5531-E711-9422-00155DC0D345");
Or HTTP Header Forwarding (Sorry I could not find one for Azure / C#)
Update:
Am assuming you are following this MSDN samples to do your CRM web api call in c# microservice. I have included our header in need - MSCRMCallerID. See if it helps you.
public async Task BasicCreateAndUpdatesAsync()
{
Console.WriteLine("--Section 1 started--");
string queryOptions; //select, expand and filter clauses
//First create a new contact instance, then add additional property values and update
// several properties.
//Local representation of CRM Contact instance
contact1.Add("firstname", "Peter");
contact1.Add("lastname", "Cambel");
HttpRequestMessage createRequest1 =
new HttpRequestMessage(HttpMethod.Post, getVersionedWebAPIPath() + "contacts");
createRequest1.Content = new StringContent(contact1.ToString(),
Encoding.UTF8, "application/json");
createRequest1.Headers.Add("MSCRMCallerID", "D994D6FF-5531-E711-9422-00155DC0D345");
HttpResponseMessage createResponse1 =
await httpClient.SendAsync(createRequest1);
if (createResponse1.StatusCode == HttpStatusCode.NoContent) //204
{
Console.WriteLine("Contact '{0} {1}' created.",
contact1.GetValue("firstname"), contact1.GetValue("lastname"));
contact1Uri = createResponse1.Headers.
GetValues("OData-EntityId").FirstOrDefault();
entityUris.Add(contact1Uri);
Console.WriteLine("Contact URI: {0}", contact1Uri);
}
else
{
Console.WriteLine("Failed to create contact for reason: {0}",
createResponse1.ReasonPhrase);
throw new CrmHttpResponseException(createResponse1.Content);
}
}
There are fews things that you have to take care while impersonating
1. To impersonate a user, set the CallerId property on an instance of
OrganizationServiceProxy before calling the service’s Web methods.
2. The user (impersonator) must have the ActOnBehalfOf privilege or be a member of the PrivUserGroup group in Active Directory
Code Example
SystemUser user = null;
user = new SystemUser(systemUser);
OrganizationServiceProxy service = CrmService.Proxy;
service.CallerID = user.Id;
Since your code is not available please ensure all the above fields are set properly
For detailed understanding use the link
https://crmbusiness.wordpress.com/2015/07/21/crm-2015-understanding-impersonation-in-plugins-and-knowing-when-to-use-it/

How to add more parameters to a JSON WebService without breaking call from old clients?

I want to add more parameters to my JSON WebService without breaking call from old clients.
Example:
My WebService in Service.asmx
[WebMethod]
public string Ping(string msg, string additionalInfo)
{
if (string.IsNullOrEmpty(additionalInfo))
{
return "Process msg with old version";
}
return "Process msg with new version"; ;
}
//My old web service does not have additionalInfo arguments
//public string Ping(string msg) {..}
Web.config tells that my WebService is JSON-based
<system.web.extensions>
<scripting>
<webServices>
<jsonSerialization maxJsonLength="50000000" />
</webServices>
</scripting>
if clients call my new Json WebService with all the parameters => everything is fine
CallWs("http://localhost:48918/Service.asmx/Ping", '{"msg":"hello", "additionalInfo":""}')
But all the current clients won't give the additionalInfo:
CallWs("http://localhost:48918/Service.asmx/Ping", '{"msg":"hello"}')
my new WebService will immediately return error:
string(654) "{"Message":"Invalid web service call, missing value for parameter: \u0027additionalInfo\u0027.","StackTrace":" at System.Web.Script.Services.WebServiceMethodData.CallMethod(Object target, IDictionary`2 parameters)\r\n at System.Web.Script.Services.WebServiceMethodData.CallMethodFromRawParams(Object target, IDictionary`2 parameters)\r\n at System.Web.Script.Services.RestHandler.InvokeMethod(HttpContext context, WebServiceMethodData methodData, IDictionary`2 rawParams)\r\n at System.Web.Script.Services.RestHandler.ExecuteWebServiceCall(HttpContext context, WebServiceMethodData methodData)","ExceptionType":"System.InvalidOperationException"}"
So my customers will have to change theire code in order to use my new WebService, I don't want that. I want to give default values to my new WebService, What is the best way to do?
Possible duplication: Can I have an optional parameter for an ASP.NET SOAP web service
But none of the response works for me.
FYI
my customers often call my JSON WebService via PHP, they simply make a POST request to the service endpoint:
$ch = curl_init("http://localhost:48918/Service.asmx/Ping");
$wsrq = array(
"msg" => "Hello",
//"additionalInfo" => "World",
);
curl_setopt_array($ch, array(
CURLOPT_POST => TRUE,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_SSL_VERIFYPEER => FALSE,
CURLOPT_POSTFIELDS => json_encode($wsrq),
CURLOPT_HTTPHEADER => array("Content-type: application/json; charset=utf-8"),
));
$response = curl_exec($ch);
It looks like the proper way to achieve that is to use method overloads for your service methods. Also for the future methods I would recommend you using models:
public class MyModel
{
public string Message { get; set; }
public string AdditionalInfo { get; set; }
}
and then:
[WebMethod]
public string Ping(MyModel model)
{
...
}
This will give you more flexibility because you will be able to add properties easily in the future without breaking.
This being said, there's one approach or a workaround that you might consider: manual deserialization (I totally don't recommend it but worth mentioning).
Make your WebMethod without any parameters:
[WebMethod]
public string Ping()
and then read the request body manually by accessing the input stream:
[WebMethod]
public string Ping()
{
Context.Request.InputStream.Seek(0, SeekOrigin.Begin);
using (var inputStream = Context.Request.InputStream)
using (var reader = new StreamReader(inputStream))
{
string body = reader.ReadToEnd();
// TODO: Worth checking the request headers before attempting JSON deserialization
// For example the Content-Type header
var model = JsonConvert.DeserializeObject<MyModel>(body);
if (string.IsNullOrEmpty(model.AdditionalInfo))
{
return "Process msg with old version";
}
return "Process msg with new version"; ;
}
}
To avoid mixing multiple responsibilities in your service method you could move the parsing of the body stream into some separate extension method:
public static class RequestExtensions
{
public static T ParseRequest<T>(this HttpRequest request)
{
request.InputStream.Seek(0, SeekOrigin.Begin);
using (var inputStream = request.InputStream)
using (var reader = new StreamReader(inputStream))
{
string body = reader.ReadToEnd();
return JsonConvert.DeserializeObject<T>(body);
}
}
}
and then your WebMethod:
[WebMethod]
public string Ping()
{
var model = Context.Request.ParseRequest<MyModel>();
if (string.IsNullOrEmpty(model.AdditionalInfo))
{
return "Process msg with old version";
}
return "Process msg with new version"; ;
}
Now clients can call the Ping method like that:
POST /WebService1.asmx/Ping HTTP/1.1
Content-type: application/json; charset=utf-8
Host: localhost:14529
Content-Length: 61
{
"msg": "Hello",
"additionalInfo": "add info"
}
or the old way:
POST /WebService1.asmx/Ping HTTP/1.1
Content-type: application/json; charset=utf-8
Host: localhost:14529
Content-Length: 26
{
"msg": "Hello"
}

Categories