Showing posts with label Web Services. Show all posts
Showing posts with label Web Services. Show all posts

Wednesday, September 12, 2018

Call a REST API from .NET

To call a REST API using the "GET" ("POST" is beyond the scope of this article) HTTP verb, use the following code:

Using Client As New System.Net.WebClient

    Dim Results As String =
        Client.DownloadString("API URL")

End Using

This will populate the Results variable with the output of the web service call.  Almost invariably, this will be in the JSON format.  Note that all input is contained in query parameters in the API URL.

Now we need to get the JSON into a .NET object that can be easily read.  This process is called deserialization.  You can do this using native .NET classes, but oddly, Microsoft seems to prefer that you use the JSON.net library.  This can be downloaded at https://www.newtonsoft.com/json.

Next you will need to create a series of classes that correspond to the structure of the JSON output.  For example, if your output JSON looks like this:

{"query":{
    "count":1,
    "created":"2018-09-07T15:51:40Z",
    "lang":"en-US"
    }
}

You should create two classes like this:

Public Class Output
    Public Property Query As QueryDetails
End Class

Public Class QueryDetails
    Public Property Count As Integer
    Public Property Created As DateTime
    Public Property Lang As String
End Class

Then, simply write the following code to deserialize the JSON output into the .NET Output object:

Dim MyOutput As New Output
Newtonsoft.Json.JsonConvert.PopulateObject(Results, MyOutput)

Wednesday, March 30, 2016

Web Services cannot find dll's in nested bin folders

When you create a new project and use a Class Library project type, the default configuration is to put the dll's in a folder named bin/release or bin/debug depending on your current build.  Then if you add a web service (either asmx or wcf), the web service will not be able to find the dll's because they are nested.  You must change the Project Properties, Compile tab, Build Output Path to just "/bin".

If you fail to do this, when you right click on the web service, and click View in Browser, you will get a parser error saying:  Could not create type.

This is a rare problem, because most of the time, you are creating a web project type that makes this configuration for you.

Thursday, January 22, 2015

SOAP and REST

I found a really great article that very simply describes the difference between SOAP and REST.  Most articles you read are written by folks that know a lot about one protocol but not the other.  And usually they are very biased toward REST.  For example, you'll see articles like "Why SOAP sucks" and the like.

In my experience SOAP is easier to create clients with if you have an IDE that can generate code using the WSDL file.

REST is easier to create clients with if you're calling the web service from JavaScript or you don't have code generation tools.

http://blog.smartbear.com/apis/understanding-soap-and-rest-basics/

Wednesday, September 10, 2014

Create a secure ASP.NET Web Api web service from scratch.

In a previous post, I described how to create a secure WCF web service.  Here, I will describe how to set up the same type of service using ASP.NET Web Api technology.

Initial Setup

First, create a new project using the ASP.NET Web Application template.  Choose the Web Api project template with No Authentication.

In the Web tab of the Project Properties, change it to Local IIS, and create a virtual directory.  Then change the application pool of this virtual directory to a pool that uses your own more powerful account.  While, you're in there, require SSL.

At this point, make sure you're everything is working by browsing to the api using this uri:  NameOfSite/api/values/5.  If you get json in return, you're good so far.

Authentication

Authentication and authorization can be configured automatically for you if you choose Local Accounts when you add the project.  However, if you want to implement your own custom authentication and authorization code, you need to jump through some hoops.

Authentication of a Web Api is done through what is called an Http Module.  This is a bit of code that you write that actually runs on IIS before the request hits your web service.  It's pretty heavy stuff, but is the only way I know how to do this at the time of this writing.  Add an Infrastructure folder to your project and add a class that implements the IHttpModule interface.  Like this:
Imports System.Net.Http.Headers
Imports System.Security.Principal

Public Class BasicAuthHttpModule
    Implements IHttpModule

    Public Sub Dispose() Implements IHttpModule.Dispose

    End Sub

    Public Sub Init(context As HttpApplication) Implements IHttpModule.Init
        AddHandler context.AuthenticateRequest, AddressOf OnAuthenticateRequest
        AddHandler context.EndRequest, AddressOf OnEndRequest
    End Sub

    Private Sub SetPrincipal(principal As IPrincipal)

        Threading.Thread.CurrentPrincipal = principal

        If HttpContext.Current IsNot Nothing Then
            HttpContext.Current.User = principal
        End If

    End Sub

    Private Function CheckUserNameAndPassword(userName As String, password As String) As Boolean

        If userName = "Steve" And password = "topsecret" Then
            Return True
        Else
            Return False
        End If

    End Function

    Private Function AuthenticateUser(userNamePasswordCombo As String) As Boolean

        Dim Validated As Boolean = False

        Try
            Dim MyEncoding = Encoding.GetEncoding("iso-8859-1")

            userNamePasswordCombo = MyEncoding.GetString(Convert.FromBase64String(userNamePasswordCombo))

            Dim Separator As Integer = userNamePasswordCombo.IndexOf(":")
            Dim UserName As String = userNamePasswordCombo.Substring(0, Separator)
            Dim Password As String = userNamePasswordCombo.Substring(Separator + 1)

            Validated = CheckUserNameAndPassword(UserName, Password)

            If Validated Then

                Dim MyIdentity = New GenericIdentity(UserName)
                SetPrincipal(New GenericPrincipal(MyIdentity, Nothing))

            End If

        Catch ex As Exception
            Validated = False
        End Try

        Return Validated

    End Function

    Private Sub OnAuthenticateRequest(sender As Object, e As EventArgs)

        Dim MyRequest As HttpRequest = HttpContext.Current.Request
        Dim AuthHeader As String = MyRequest.Headers("Authorization")

        If AuthHeader IsNot Nothing Then

            Dim AuthHeaderValue = AuthenticationHeaderValue.Parse(AuthHeader)

            If AuthHeaderValue.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase) And AuthHeaderValue.Parameter IsNot Nothing Then
                AuthenticateUser(AuthHeaderValue.Parameter)
            End If

        End If

    End Sub

    Private Sub OnEndRequest(sender As Object, e As EventArgs)

        Dim MyResponse As HttpResponse = HttpContext.Current.Response

        If MyResponse.StatusCode = 401 Then
            MyResponse.Headers.Add("WWW-Authenticate", "Basic realm=""My Realm""")
        End If

    End Sub

End Class



To enable IIS to use this HTTP Module, add the following to your web.config:
<system.webServer>
    <modules>
      <add name="BasicAuthHttpModule"
        type="NameOfWebApplication.BasicAuthHttpModule, NameOfWebApplication"/>
    </modules>
</system.webServer>

In IIS, you also need to disable all authentication including anonymous on the site where the web api is hosted.

Do a quick test in the browser.  You should find that a login window comes up (in IE anyway) with which to provide your credentials.

Next, you'll want to create a little client application to test the web service.  A console application works great for this.  However, you will need to install a NuGet package called Microsoft ASP.NET Web API Client Libraries.  The id is Microsoft.AspNet.WebApi.Client.  Here is the code to call the Get method from your console:
    Async Function WebApiGetAsync() As Task

        Using Client As New HttpClient()

            Client.BaseAddress = New Uri("https://NameOfServer.Domain.com/NameOfSite/")
            Client.DefaultRequestHeaders.Accept.Clear()
            Client.DefaultRequestHeaders.Accept.Add(New MediaTypeWithQualityHeaderValue("application/json"))

            Dim ByteArray = Encoding.ASCII.GetBytes("Steve:topsecret")
            Client.DefaultRequestHeaders.Authorization = New AuthenticationHeaderValue("Basic", Convert.ToBase64String(ByteArray))

            Dim Response As HttpResponseMessage = Await Client.GetAsync("api/values/5")

            If Response.IsSuccessStatusCode Then

                Dim Results As String = Await Response.Content.ReadAsStringAsync()

                Console.WriteLine(Results)

            Else

                Console.WriteLine(Response.StatusCode.ToString)
                Dim Results As String = Await Response.Content.ReadAsStringAsync()
                Console.WriteLine(Results)

            End If

        End Using

    End Function

Authorization

To create your own custom authorization, you just need to create your own Authorization Filter.  Just add a class called CustomAuthorizeAttribute to your Infrastructure folder like this:
Public Class CustomAuthorizeAttribute
    Inherits System.Web.Http.AuthorizeAttribute

    Protected Overrides Function IsAuthorized(actionContext As Http.Controllers.HttpActionContext) As Boolean
        Dim Authorized As Boolean = False

        For Each ThisRole As String In Roles.Split(",").ToList

            If SomeCustomLibrary.IsInRole(Threading.Thread.CurrentPrincipal.Identity.Name, ThisRole) Then
                Authorized = True
                Exit For
            End If

        Next

        Return Authorized
    End Function

End Class

There are a few things to note here.

  1. You must inherit System.Web.Http.AuthorizeAttribute and not System.Web.Mvc.AuthorizeAttribute.  
  2. This is invoked by using <CustomAuthorize(Roles:="RoleName1, RoleName2")>
How to call a POST from a client

To call a POST method from your client, do the following:
    Async Function WebApiPostAsync() As Task

        Using Client As New HttpClient()

            Client.BaseAddress = New Uri("https://NameOfServer.YourDomain.com/NameOfSite/")

            Dim MyModel As New ParameterModel With {.Parameter1 = "From the Web Service.", .Parameter2 = 484507, .Parameter3 = Now.Date}

            Dim ByteArray = Encoding.ASCII.GetBytes("Steve:topsecret")
            Client.DefaultRequestHeaders.Authorization = New AuthenticationHeaderValue("Basic", Convert.ToBase64String(ByteArray))

            Dim Response As HttpResponseMessage = Await Client.PostAsJsonAsync("api/NameOfController", MyModel)

            If Response.IsSuccessStatusCode Then

                Dim Results As String = Await Response.Content.ReadAsStringAsync()

                Console.WriteLine(Results)

            Else

                Console.WriteLine(Response.StatusCode.ToString)
                Dim Results As String = Await Response.Content.ReadAsStringAsync()
                Console.WriteLine(Results)

            End If

        End Using

    End Function

There are some key differences here between WCF and Web API.  With WCF, you can just call a function and specify parameters just like you would if the method was local.  With Web API, in cases of GETs the parameters are either provided via the URL with query parameters or the route.  With POSTs you pass a JSON object as data.  This JSON object has a "property" for each parameter that you need.





Thursday, September 4, 2014

Use SoapUI to test your web services

Recently, I created a web service that external organizations would use as an API to access our data.  I decided to use WCF and the SOAP architecture to achieve this.  I also created a little .NET console app to test the web service.  Everything worked great.  However, I was a little concerned that I might have unintentionally wrote some code that was only supported by the Microsoft stack.  How could I be sure that my web service could be used by someone using a different platform?

My first idea was to try to access the web service using jQuery.  I felt that if I could successfully invoke the web service using jQuery or JavaScript, this would demonstrate that Microsoft technology wasn't necessary to use my service.  However, I found that calling a SOAP based web service from client side scripting is not an easy task.  In fact, I never got this to work, and there is not a lot of information out there about how to do this.  One problem is that browsers have security measures to stop cross site scripting.  So the web service has to be part of the same site.  If you can get past that, you also have to manually build the SOAP envelope in your javascript which is tedious.  On top of that you have to figure out how to specify your logon credentials within the request.  I concluded that it is just not practical to call a SOAP service from the client.  I think this is where the advantages of using a REST style web service really pay off.

I discovered a widely used application called SoapUI.  It is a free download, and you can use it to invoke the methods of your web service for testing purposes.  This is a way to independently test your web service operations without being bound to Microsoft.

Installation is straight forward and instructions can be found on the site.  Note that you do not need to install Hermes if you're not testing a Java service.

Here is how to test a web service operation.

  1. Open SoapUI and right click on Projects, New SOAP Project.
  2. Name the project and specify the address of the WSDL of your service.
  3. Check the box that says Create Sample Requests.
  4. In the navigator, you'll see each operation of the service.  If you click the plus sign next to each operation you will see the sample request that was created for you.
  5. Click on the request and find the request properties.
  6. If the web service security is set up for TransportWithMessageCredential, then set the username and password properties to something that works.  Also set the WSS-Password Type to PasswordText.
  7. Double-click the request.
  8. In the request window, you'll see the SOAP envelope.  You can see where the parameters go, and can set these here.
  9. Then, click the green arrow which sends the request to the web service.
  10. The results will be shown in the right-hand pane.

Friday, July 18, 2014

WCF and ASP.NET Web API

When writing web services using .NET technology, you have a few options.

WCF:  Tried and true, this is the successor to the original ASMX style web services.  WCF has capabilities to create any type of service that can be accessed over a network, not just HTTP.  WCF tends to be a bit configuration heavy requiring the use of a special tool in Visual Studio to set the myriad of options up.  WCF generally uses the SOAP protocol.  While it is possible to create a REST style web service using WCF, it is recommended by Microsoft to use Web API technology for REST style web services.

ASP.NET Web API:  Web API allows you to create a REST style web service (no SOAP support) using an architecture that is very similar to MVC.  You create a set of controllers and configure a routing system to create your operations and accept parameters.  This tends to be simpler to setup and configure as it is very close to what you would do to configure an MVC web application.

There are also a couple of terms that need to be defined:

REST style web service:  When you apply a REST architecture to a web service, this changes the way in which the client calls the operations of the web service.  With a traditional web service, each call is represented by an HTTP GET verb with the URL that represents the name of the operation.  With a REST style web service, the URL represents a set of operations that are differentiated by the use of 4 different HTTP verbs (GET, POST, PUT, and DELETE).   I believe that the advantage of REST is that it is (allegedly) easier and makes more sense when called from client side code like JavaScript.  Therefore, it can be consumed by a wider variety of devices.  Also REST is necessary when implementing a data API using the OData protocol.

OData Protocol:  OData is set of specifications for creating a data API.  A data API is a service that exposes your database more directly over the web.  Instead of abstract operations that perform CRUD operations on the callers behalf, the data API allows the consumer to perform the CRUD operations nearly directly.  For example, each table could be represented as a URL and the user could query the table directly or even join together multiple tables.  This has the effect of moving the business logic to the client.

Conclusions

Using OData to access a database over the internet sounds like it could be useful.  And obviously you need to use REST in order to implement this.  However, setting up an OData endpoint using Web API is anything but straight forward right now.  In my opinion, this technology needs to mature a little longer for me to recommend using it.

If you're not going to use OData, then I just don't see the big advantage of using REST.  It seems to me that it would be awkward to organize your operations into sets of four HTTP verbs.  Another big disadvantage to using REST, is that there is no automatic proxy generation for consumers of REST style services.  Web API does seem like it works well, but it requires a REST style architecture.

This is my bottom line:  If you want to use a REST architecture, use Web API.  If you want to use a SOAP architecture, then use WCF.  At this time, I will continue to use SOAP and therefore WCF.

Thursday, May 23, 2013

Be mindful of transactions when logging exceptions to a database

The idea behind transactions is this:  When an exception occurs, any database updates defined within the scope of a transaction are not committed to the database.  Either everything works or everything doesn't.  If you are logging exceptions to a database, you have to make sure your logging code is not within the scope of the transaction where the exception is occurring.

This can be tricky when using web services.  I would advise not using the System.EnterpriseServices.TransactionOption web method attribute to implement transactions.  This essentially includes the entire web method within the scope of a transaction.  So, your logging code would also be included in this transaction and would not be committed if an exception occurs.

Replace the attribute with the following code:

Try

    Dim MyOptions As New System.Transactions.TransactionOptions
    Dim MyScopeOption As New System.Transactions.TransactionScopeOption
    MyOptions.IsolationLevel = Transactions.IsolationLevel.ReadCommitted

    Using scope As System.Transactions.TransactionScope = _
      New System.Transactions.TransactionScope(Transactions.TransactionScopeOption.Required, _
      MyOptions)

        ...
        Code goes here
        ...

        scope.Complete()

    End Using

Catch ex As Exception

    ...
    Database logging code goes here
    ...

End Try

Note that when an exception occurs, you leave the scope of the transaction, and are free to commit your logging entries in the database.