WebApi
Part II

Dec 19

Introduction

My previous post explored the plumbing of creating a WebApi service within Sitefinity. It was long enough, and so I left the optional enhancements for this follow-up post.

We should consider, 'Who is consuming our API?'. Most of the time, our JavaScript will be used within a widget we have developed. Maybe as a service for others. But whoever it is, they need to handle when things go wrong on our end, and what to expect, so they know how to handle it. To help with this, I create and wrap my WebApi responses in a special class. I also like to pass an error code so that they can report an issue and, just as important, so that I can look it up.

Response Class

Here is my Response class.

public class Response
{
    public Boolean Success { getset; } = true;
    public String Message { getset; } = "OK";
    public String Code { getset; } = "E0000";
    public dynamic Data { getset; }
}

By default, everything is a success, and the Data field is the requested result that I pass back. It is a dynamic field, meaning I can assign any object to it I want at runtime.

If an error happens at my end, I still want to ensure I send back a successful HTTP response with an expected structure to the requestor. This means that in their code, the only exception they need to consider is if there is a Network issue. Otherwise, they just need to check the 'Success' boolean field and act accordingly.

Let's update our controller's method to use this.

[HttpGet]
[Route("get-stuff/{topic}")]
public IHttpActionResult GetSomeStuff(String topic)
{
    String cacheKey = "Stuff-" + topic;
    var response = (Response)CacheManager[cacheKey];
    if (response == null || !response.Success)
    {
        response = new Response();
        try
        {
            response.Data = stuffService.GetStuff(topic);
            CacheManager.Add(cacheKey, response, CacheItemPriority.Normal, nullnew AbsoluteTime(TimeSpan.FromHours(1)));
        }
        catch(Exception ex)
        {
            response = stuffService.RecordError(ex);
        }
                
    }
 
    return this.Json(response);
}

I wrap the code in a try-catch to ensure any issues are captured and the requestor does not get a server error. I also add a RecordError method to my service.

public Response RecordError(Exception ex)
{
    Random random = new Random();
    Response response = new Response();
    response.Success = false;
    response.Message = ex.Message;
    response.Code = "E" + random.Next(1000, 9999).ToString();
 
    ApplicationException apEx = new ApplicationException(response.Code, ex);
    Log.Write(apEx);
 
    return response;
}

In this record error method, I generate a random number. I then create a new Exception, add this number to the message and populate the InnerException with the original exception. Finally, I ensure it is recorded in the Sitefinity logs and then send the response back. Now the requestor knows that something has gone wrong, without having to catch issues on their end, and they have a unique-ish number that they can report back to me that I can search and find in my logs.

True, the number is not unique, but unique enough that I hope, given a reported day, I can find the exact log entry. You could also extend this to record more developer-helpful details for the log and better error messages for the requestor.

Security

A common requirement is that we want to restrict access to our API. Easy as.

var identity = ClaimsManager.GetCurrentIdentity();
if (!identity.IsAuthenticated)
{
    return StatusCode(System.Net.HttpStatusCode.Forbidden);
}
Make sure this security check is done outside of your cache method. It is something that should run on every request.

If this was a highly accessed API and performance was a factor, you should look at running your security check in the OWIN pipeline. ... Sounds like another topic for a blog post.

Async WebApi

Another common scenario is that our API is a gateway to a third-party service, and we have them call our API so that we can then call and connect to the third party. But this call takes time, and we do not want to hold up a .NET thread doing nothing while we wait for a response. It might as well serve another request and come back to this one when we have a response back. This is known as an asynchronous method.

First, we change our method signature.

public async Task<IHttpActionResult> GetSomeStuff(String topic)

Then we update our service request.

var task = await stuffService.GetStuff(topic);
response.Data = task;

Then to make our GetStuff method ok and compile, we just need to change it's signature to:

public async Task<List<Stuff>> GetStuff(String stuffTopic)
Buuuuut, there is an issue here.

It'll compile, but we are left with a warning message from Visual Studio - "This async method lacks 'awaiter' operators and will run synchronously."

That defeats our goal.

If we were making an HTTP request to a third party, as described in our scenario, you would have a method to make that call asynchronously, and then you will be fine. If you are calling a database, you will also find async methods to do that as well.

HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create("https://the-url");
using (Stream stream = await webRequest.GetRequestStreamAsync())
{
    ...
}

But if you are not doing any of these things, what can you do?

Do you really need to call this method asynchronously? Keep in mind that there are overheads to async calls. If your method is local and fast, making it async may worsen performance.

You can call the method using Task.Run(). This is a direct way to spawn a new thread and run your method on that thread. First, roll back the async 'GetStuff' method we created. Then in the controller, call it like this.

var task = Task.Run<List<Stuff>>(() => stuffService.GetStuff(topic));
response.Data = task.Result;

By using this process, you can call any non-async method asynchronously.

Be aware. If you are calling the Sitefinity Manager factories inside of an async method, you may not get the results you are expecting. Keep in mind that Sitefinity is running in a HttpContext on the current thread. When pushed onto another thread, the managers (like the DynamicModuleManager) may not have access to everything it needs. As an example, I found a scenario that always returned an empty result when calling 'GetChildItems()'. So test, test, test.

Darrin Robertson - Sitefinity Developer

Thanks for reading and feel free to comment - Darrin Robertson

If I was really helpful and you would buy me a coffee if you could, yay! You can.


Leave a comment
Load more comments

Make a Comment

recapcha code