I have implemented a simple web service and I want to return appropriate Http Status Error codes depending on the error that occurred in the method being called.
I am using .NET framework 4.7.2 with Visual Studio 2019 and the IIS Express 10 built into Visual Studio for testing at the moment. I am using the Boomerang extension for Chrome to call the service.
I have implemented a FindPerson method that takes a name. If the person is not found, or more than one person is found, I want to return a "Not Found" response (404).
I have implemented a simple ServiceError class that I am using to throw a WebFaultException along with a Not Found error code. When I throw the WebFaultException the appropriate fault response is sent to the consumer (I see the details of the problem) but the http status is 500 (internal service error) instead of the 404 error I used (and expected to be received)
Here is my simple FindPerson method:
Private Function FindPerson(ByVal name As String) As Person Implements MyService.FindPerson
Dim foundPerson As Person = Nothing
Dim people = GetPeople(name)
Select Case people.Count
Case Is > 1
Throw New WebFaultException(Of ServiceError)(New ServiceError("More than 1 person found with the name provided.", ""), HttpStatusCode.NotFound)
Case Is = 0
Throw New WebFaultException(Of ServiceError)(New ServiceError("No Person with the name provided was found.", ""), HttpStatusCode.NotFound)
Case Else
foundPerson = people(0)
End Select
Return foundPerson
End Function
Here is my ServiceError class:
<DataContract>
Public Class ServiceError
<DataMember>
Public Property ErrorInfo As String
<DataMember>
Public Property ErrorDetails As String
Public Sub New(ByVal info As String, ByVal details As String)
Me.ErrorInfo = info
Me.ErrorDetails = details
End Sub
Public Sub New()
End Sub
End Class
This is the response I get. Like I said, the details are correct... but the Http Status Error code is 500 instead of 404:
<s:Envelope
xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">http://www.w3.org/2005/08/addressing/soap/fault</a:Action>
</s:Header>
<s:Body>
<s:Fault>
<s:Code>
<s:Value>s:Sender</s:Value>
<s:Subcode>
<s:Value
xmlns:a="http://schemas.microsoft.com/2009/WebFault">a:NotFound
</s:Value>
</s:Subcode>
</s:Code>
<s:Reason>
<s:Text xml:lang="en-US">Not Found</s:Text>
</s:Reason>
<s:Detail>
<ServiceError
xmlns="http://schemas.datacontract.org/2004/07/My_Web_Service"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<ErrorDetails/>
<ErrorInfo>No Person with the name provided was found.</ErrorInfo>
</ServiceError>
</s:Detail>
</s:Fault>
</s:Body>
</s:Envelope>
Edit:
I found a solution to my problem but it has only made me realize I need to find a better solution.
The solution involved implementing a method called RaiseWebException that set the outgoing response status code to the status that was also set in the WebFaultException:
Private Sub RaiseWebException(ByVal status As HttpStatusCode, ByVal info As String, ByVal details As String)
WebOperationContext.Current.OutgoingResponse.StatusCode = status
Throw New WebFaultException(Of ServiceError)(New ServiceError(info, details), HttpStatusCode.NotFound)
End Sub
I called the method when I failed to find my person:
Private Function FindPerson(ByVal name As String) As Person Implements MyService.FindPerson
Dim foundPerson As Person = Nothing
Dim people = GetPeople(name)
Select Case people.Count
Case Is > 1
RaiseWebException(HttpStatusCode.NotFound, "More than 1 person found with the name provided.", "")
Case Is = 0
RaiseWebException(HttpStatusCode.NotFound, "Person with the name provided was found.", "")
Case Else
foundPerson = people(0)
End Select
Return foundPerson
End Function
This works well when calling the method from the browser using Boomerang; however, when I test calling the method in a vb.net consuming test application, I am not seeing the service error details. I am getting the generic 404 error with a generic error message that states that "the endpoint couldn't be reached" instead of "the person wasn't found". This will be confusing for anyone calling the service through a consuming application implemented with .NET
I'm not sure this is the best solution for error management in my service going forward.
I am open to any suggestions on the best practices for error management in .NET web services.
Thank you
I found a solution to my original question: Set the OutGoingResponse.StatusCode to the same status code I used for the WebFaultException
The solution involved implementing a method called RaiseWebException that set the outgoing response status code to the status that was also set in the WebFaultException:
Private Sub RaiseWebException(ByVal status As HttpStatusCode, ByVal info As String, ByVal details As String)
WebOperationContext.Current.OutgoingResponse.StatusCode = status
Throw New WebFaultException(Of ServiceError)(New ServiceError(info, details), status)
End Sub
I call the method when I failed to find my person:
Private Function FindPerson(ByVal name As String) As Person Implements MyService.FindPerson
Dim foundPerson As Person = Nothing
Dim people = GetPeople(name)
Select Case people.Count
Case Is > 1
RaiseWebException(HttpStatusCode.NotFound, "More than 1 person found with the name provided.", "")
Case Is = 0
RaiseWebException(HttpStatusCode.NotFound, "Person with the name provided was found.", "")
Case Else
foundPerson = people(0)
End Select
Return foundPerson
End Function
This works well when calling the method from the browser using Boomerang; however, when I test calling the method in a vb.net consuming test application, I am not seeing the service error details. I am getting the generic 404 error with a generic error message that states that "the endpoint couldn't be reached" instead of "the person wasn't found". This will be confusing for anyone calling the service through a consuming application implemented with .NET.
So, while this is the answer to my original question/problem, it appears to be inadequate for the overall project. Best of luck to anyone else facing the same thing.
Related
I have written a VB.Net Visual basic console application for Self hosting a custom file upload service to be consumed by an application. Concept being the end user uses the application to generate data, when completed the file is uploaded to our server without user intervention. I have complete control over both applications. The problem is I can't figure out the POST Upload signature that can accept several params, including the file or how to actually receive the file. The User application is in beta now, testing all other functionality excluding the "Send File" sub's. I've never seen a file larger then 180 KB; I plan on accepting files sizes up to 1 MB. This lets me place some limitations (and filters) to help avoid abuse of the service.
I'm using NuGet packages webapi.client (4.0.30506), webapi.selfhost (4.0.3056) (and their associated required packages) and newtonsoft.json (4.5.11) and PostMan to test/debug the process. I'm using Visual Studio 2019 (Fully patched and up to date). All of the examples and google research point only to C# (not my language of choice), or are for hosted solutions like IIS.
In Postman, the only place where filenames are accepted are in the body, form-data. So, there is where I set up my key/value pairs with matching (including case and order) the params as defined in the FileULRequest class.
Everything that I've tried returns either
'500 internal server error'
or
"Message": "No HTTP resource was found that matches the request URI 'http://10.0.1.102:21212/file/upload/'."
The class object of the request looks like this:
Public Class FileULRequest
Public Property EncToken As String 'Holds an encrypted token for authorization
Public Property Filename As String 'Holds a recommended file name
Public Property AppID As String 'Holds the client/app ID for simpler server actions
Public Property File As Byte() 'Not sure if this is the right type/ should be the encrypted file contents.
End Class
The POST function signature currently looks like this:
Imports System.Web.Http
Namespace Controllers
Public Class FileController
Inherits ApiController
Public Function PostUpload(<FromBody()> ByVal ObjRequest As FileULRequest) As String
Return ""
End Function
End Class
End Namespace
In the Sub Main I have: (note, this is cleaned out)
Sub Main()
API_URL = Dns.GetHostByName(Dns.GetHostName()).AddressList(0).ToString()
Dim ThisConfig As New HttpSelfHostConfiguration("HTTP://" & API_URL & ":" & API_PORT)
ThisConfig.Routes.MapHttpRoute(name:="FileUpload", routeTemplate:="{controller}/{ObjRequest}", defaults:=New With {.id = RouteParameter.Optional})
ThisConfig.MaxConcurrentRequests = API_MaxCon
Dim Config As HttpSelfHostConfiguration = ThisConfig
Using Webserver As New HttpSelfHostServer(Config)
Try
Webserver.OpenAsync().Wait() 'Start the web server
Console.WriteLine("Listening at: " & API_URL & ":" & API_PORT) 'use the URL & port defined
Console.WriteLine("Enter to end")
Catch ex As Exception
Console.WriteLine("Error:{0}", ex.Message.ToString)
Console.WriteLine("Enter to end")
Console.ReadLine()
End
End Try
Dim Cmd As String = UCase(Console.ReadLine())
End
End Using
End Sub
API_Port and API_MaxCon are properties stored in the Appsettings.
What I'm trying to do is set the FileULRequest object params, post this to the service, confirm & validate the data and, if successful, save the file onto a network share. I've tried a large number of different combinations and nothing seems to get close; I cant get inside the Post event in the debugger to figure out or test anything.
I'm writing a REST Service in vb.net that I want to allow creation of items and I was wondering which is the best way of returning the 201 Status Code (or 403 if it fails) to the client?
My example code would be:
<WebInvoke(method:="POST", UriTemplate:="create/{var}", BodyStyle:=WebMessageBodyStyle.Bare, RequestFormat:=WebMessageFormat.Xml, ResponseFormat:=WebMessageFormat.Xml)>
Public Function CreateTest(ByVal Var As String) As Boolean
Dim Result As Boolean = True
'do my processing here and then...
Throw New WebFaultException(Net.HttpStatusCode.Created)
Return Result
End Function
So is Throw New WebFaultException(Net.HttpStatusCode.Created) the correct way to do this?
Does the twilio asp.net helper library package NOT work in vb.net? I can get it to work in c# web app but not vb.net web app.
In a vb.net web application project the following code doesnt send an sms message and when stepping through with the debugger, errs on the send message line and brings up a file dialog asking for access to core.cs. The twilio library's were installed via nuget.
Public Shared Sub SendAuthCodeViaSms(ByVal number As String)
Dim twilioAccountInfo As Dictionary(Of String, String) = XmlParse.GetAccountInfoFromXmlFile("twilio")
Dim accountSid As String = twilioAccountInfo("username")
Dim authToken As String = twilioAccountInfo("password")
If (Not String.IsNullOrEmpty(accountSid) AndAlso Not String.IsNullOrEmpty(authToken)) Then
Dim client = New TwilioRestClient(accountSid, authToken)
client.SendMessage(TwilioSendNumber, ToNumber, "Testmessage from My Twilio number")
Else
'log error and alert developer
End If
End Sub
But in a C# web API project the same code sends the message as expected.
protected void Page_Load(object sender, EventArgs e)
{
const string AccountSid = "mysid";
const string AuthToken = "mytoken";
var twilio = new TwilioRestClient(AccountSid, AuthToken);
var message = twilio.SendMessage(TwilioSendNumber,ToNumber,"text message from twilio");
}
and all the sid's and tokens and phone number formats are correct, otherwise the c# one wouldnt send and I wouldnt get to the client.SendMessage part of vb.net version (client.SendSMSMessage produces the same result)
Twilio evangelist here.
I tried our your code by creating a simple VB console app and it worked for me.
The only thing I can think of is that either you are not getting your Twilio credentials correctly when parsing the XML, or the phone number you are passing into the function is not formatted correctly.
I'd suggest putting the result of call to SendMessage() into a variable and checking to see if RestException property is null:
Dim result = client.SendMessage(TwilioSendNumber, ToNumber, "Testmessage from My Twilio number")
If (Not IsNothing(result.RestException)) Then
' Something bad happened
Endif
If Twilio returns a status code greater than 400, then that will show up as an exception in the RestException property and will give you a clue as to whats going on.
If that does not work, you can always break out a tool like Fiddler to watch and see if the library is making the property HTTP request and Twilio is returning the proper result.
Hope that helps.
I have a windows service that I have been writing in Vb.Net. As part of this service it calls a class that has a long running Process.
I can execute commands to this process when I want to via the ServerCommands() class within the service, however I want to call these remotely. Possibly from a website or click once WPF application.
For this I have used a simple Tcp.Ip WCF example, and have verified it as working correctly.
This called OnStart()
Private _serverCommands As ServerCommands
Protected Overrides Sub OnStart(ByVal args() As String)
' Add code here to start your service. This method should set things
' in motion so your service can do its work.
Debugger.Launch()
' Action a new implementaion of the WCF Service on localhost
_host.AddServiceEndpoint(GetType(ICommunicationService), New NetTcpBinding(), String.Format("net.tcp://127.0.0.1:{0}", AppSettings.TcpServicePort))
_host.Open()
' Start the server command
_serverCommands = New ServerCommands()
_serverCommands.StartServer()
End Sub
However... when I'm calling the service through WCF its starting a new instance of the ServerCommands() Class rather than attaching to the already running thread.
The following call
Public Function DoWork() As String Implements ICommunicationService.DoWork
Dim command As String = "say hello world"
Dim service As IMinecraftService = New MinecraftService()
service.ExecuteServerSideCommand(command)
Return "Command Executed"
End Function
Implements this on the main service.
Public Sub ExecuteServerSideCommand(command As String) Implements IMinecraftService.ExecuteServerSideCommand
If (_serverCommands IsNot Nothing) Then
_serverCommands.SendCommand(command)
End If
End Sub
It appears that in debug _serverCommands is Nothing when it should be running.
How might I go about ensuring any command I execute through WCF communicates with the running instance instead of creating a new ServerCommand() instance??
I haven't tried WCF before, so I might be hitting a dead end... however I'm sure its possible.
Thanks in advance.
I found that I was calling a new instance of the MinecraftService each time I sent a command via WCF.
As Jeff rightly said, I was not making the object shared, I was only accessing a new instance of this class.
I changed it from
From
MyMainClass
Private _serverCommands As ServerCommands
My WcfService
Dim command As String = "say hello world"
MinecraftService.ServerCommands.SendCommand(command)
To
MyMainClass
Public Shared ServerCommands As ServerCommands
My WcfService
MinecraftService.ServerCommands.SendCommand(command)
Sorry, this can be a basic question for advanced VB.NET programmers but I am a beginner in VB.NET so I need your advice.
I have a web application and the login is required for some specific pages. To check if the user is logged in, the old programmer used this technique:
Dim sv As New WL.SessionVariables(Me.Context)
If Not (sv.IsLoggedIn) Then
Response.Redirect(WL.SiteMap.GetLoginURL())
End If
Well, I have to use this Logged In checking in a handler done by me and I tried this:
Public Class CustomHandler
Implements System.Web.IHttpHandler, IReadOnlySessionState
Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
Dim sv As New WL.SessionVariables(context)
If Not (sv.IsLoggedIn) Then
context.Response.Write("No access unless you're the CEO!!!" & sv.IsLoggedIn)
ElseIf sv.IsLoggedIn Then
DownloadFile(context)
Else
End If
End Sub
//other code
End Class
Well, the "is logged in" checking is always false (even after I login) and I think it's an issue with the context. So all the other pages works fine with logging checking but this handler have this specific issue.
Can you guys give a helping hand?
UPDATE:
The logged in is done trough this method:
Public Sub SetCreditialCookie(ByVal accountID As Integer)
Me.AccountID = accountID
m_context.Session.Item("loggedInAccount") = accountID
m_context.Response.Cookies.Add(New System.Web.HttpCookie("account_id", CStr(m_context.Session.Item("account_id"))))
m_context.Response.Cookies("account_id").Expires = DateTime.Now.AddDays(5)
End Sub
and to check it it's logged in, this method is called:
Public Function IsLoggedIn() As Boolean
If Not m_context.Session.Item("loggedInAccount") Is Nothing And Me.AccountID = m_context.Session.Item("loggedInAccount") Then
Return True
Else
Return False
End If
End Function
UPDATE 2:
- debugging the code shown that there were multiple kind of logins and I was checking the wrong one with the session.
Due to the use of IReadOnlySessionState, is it possible that the SessionVariables class attempts in some way to modify the Session, which in turn causes an error (possibly handled and not visible to you).
If this is the case it could mean that the IsLoggedIn property is not correctly initialised, or does not function as expected?
Do you have access to the code for the class. If so, try debugging it to see what is happening.