Archive

Archive for the ‘ASP.NET WebAPI’ Category

Authorization and public Help pages for Mobile Services written in .NET

28/02/2014 1 comment

Last week one of the most awaited feature for Azure Mobile Services has been released: the full support for writing backend logic using .NET and the ASP.NET Web API framework. We can now use Web API and Visual Studio to write, test and deploy our services. On Scott Guthrie’s blog you can read the official announcement, along with a step-by-step guide about how to use this new feature.

An interesting feature of this new support is that, because we’re working with a Web API, we can navigate to the /help page of the service to obtain the documentation of all the available methods (table functions, APIs, jobs). For each resource there are exanples of request and response messages, with a button that allows to directly invoke the function, so that it is easy to test the service.

This tool is very useful if we need to provide the documentation of our API to third parties, because all the pages are automatically generated for us.

However, it seems that the Help system only works when we execute the service within Visual Studio. In fact, after we have deployed the Mobile Service to Azure, if we try to access the /help page, we’ll obtain an HTTP erorr 403 – Forbidden. This is because, by default, all remote requests to mobile service resources are restricted to clients that present the application key.

In general, this behavior can be changed with the RequiresAuthorization attribute on contollers (or their methods). For example, we can use this attribute on a TableController like the following:

public class PeopleController : TableController<Person>
{
    [RequiresAuthorization(AuthorizationLevel.Anonymous)]
    public IQueryable<Person> Get()
    {
        ...
    }

    [RequiresAuthorization(AuthorizationLevel.User)]
    public async Task<IHttpActionResult> Post(Person item)
    {
        ...
    }
}

In this case, the Get method, that reads from the table, has anonymous access, while the Post operation, that performs an insert, requires an authenticated user. In other words, this attribute is used to specify the table methods permissions, like we do in the portal when we use Node.js for the backend.

The problem with Help is that it is provided by a class called HelpController that is part of the Azure Mobile Services SDK: so, like all the other controllers, once deployed it requires the application key, and this behavior cannot be changed because we don’t have direct access to the class (and so we can’t add a Custom Attribute to it). On the other hand, if we want to make the documentation of our service public, it must be accessed anonymously with a normal browser. Note that we have the same problem with ContentController, that is responsible for providing static content like CSS, Javascript, and so on.

The solution is to create a class that inherits from HelpController and another from ContentController, each of them with the anonymous authorization level:

[RequiresAuthorization(AuthorizationLevel.Anonymous)]
public class PublicContentController : ContentController
{
    [HttpGet]
    [Route("content/{*path}")]
    public new async Task<HttpResponseMessage> Index(string path = null)
    {
        return await base.Index(path);
    }
}

[RequiresAuthorization(AuthorizationLevel.Anonymous)]
public class PublicHelpController : HelpController
{
    [HttpGet]
    [Route("help")]
    public new IHttpActionResult Index()
    {
        return base.Index();
    }

    [HttpGet]
    [Route("help/api/{apiId}")]
    public new IHttpActionResult Api(string apiId)
    {
        return base.Api(apiId);
    }
}

As attribute routing takes precedence, we’re overwriting the default routes for /content and /help request, using controllers that allow anonymous access. We can now publish the Mobile Service to Azure and verify that the Help page can be correctly reached with a normal browser.

Note that the Help page of the deployed service has a different layout than the local one, but the information shown are the same (page layouts are embedded in the library). More important, even if the Help page is now public, other controllers mantain their own access rules, and so, for example, if we want to test a method of a TableController that has an Authorization level of Application (the default), in the Test Client Dialog we need to specify the Application Key of the Mobile Service using the X-ZUMO-APPLICATION header:

Azure Mobile Service Test Client Dialog

Azure Mobile Service Test Client Dialog

It is possible to refer to the official documentation for more information about this topic.

How to create a slideshow of images for the Windows 8.1 Lock screen

10/12/2013 2 comments

The new LockScreen.RequestSetImageFeedAsync method that is available in Windows 8.1 allows to set a remote RSS feed as source for the Lock Screen slideshow.

The RSS must have the following structure:

<?xml version="1.0" ?>
<rss version="2.0">
<channel>
	<title>Bing Images RSS feed</title>
	<link>http://mypersonalsite.azurewebsites.net/api/wallpapers</link>
	<description>Bing Images for Windows 8.1 Lock Screen</description>
	<pubDate>Sat, 07 Dec 2013 19:39:04 GMT</pubDate>
	<lastBuildDate>Sat, 07 Dec 2013 19:39:04 GMT</lastBuildDate>
	<item>
		<title>A billabong in Wooleen Station, Western Australia</title>
		<link>http://www.bing.com/az/hprichbg/rb/WooleenStation.jpg</link>
		<description>A billabong in Wooleen Station, Western Australia</description>
		<enclosure url="http://www.bing.com/az/hprichbg/rb/WooleenStation.jpg" 
                type="image/jpg" />
		<pubDate>Fri, 06 Dec 2013 00:00:00 GMT</pubDate>
	</item>
	<item>
		<!-- ... -->
	</item>
</channel>
</rss>

Images must be in JPEG or PNG format. We can easily defines a WebAPI that provides an RSS with this structure. The following example creates the feed with the last Bing images:

public class WallpapersController : ApiController
{
    private static readonly Uri BASE_URI = new Uri("http://www.bing.com");

    public async Task<HttpResponseMessage> Get(string market = null)
    {
        var response = Request.CreateResponse();

        try
        {
            using (var client = new HttpClient())
            {
                var content = await client.GetStringAsync(
                    "http://www.bing.com/HPImageArchive.aspx?format=xml&idx=0&n=8");

                var feed = new SyndicationFeed("Bing Images RSS feed", 
                    "Bing Images for Windows 8.1 Lock Screen", Request.RequestUri);
                
                feed.ElementExtensions.Add(
                    "pubDate", string.Empty, DateTimeOffset.UtcNow.ToString("r"));

                feed.LastUpdatedTime = DateTimeOffset.UtcNow;
                var items = new List<SyndicationItem>();
                feed.Items = items;

                var xml = XDocument.Parse(content);
                foreach (var image in xml.Descendants("image"))
                {
                    var description = image.Element("copyrightsource") != null ? 
                        image.Element("copyrightsource").Value : null;
                    
                    var item = new SyndicationItem(image.Element("copyright").Value, 
                        description, new Uri(BASE_URI, image.Element("url").Value));
                    
                    item.PublishDate = 
                        DateTimeOffset.ParseExact(image.Element("startdate").Value, 
                        "yyyyMMdd",
                        CultureInfo.InvariantCulture);
                    
                    item.ElementExtensions.Add(
                        new XElement("enclosure", 
                            new XAttribute("url", item.Links[0].Uri),
                            new XAttribute("type", "image/jpeg")));

                    items.Add(item);
                }

                using (var ms = new MemoryStream())
                {
                    using (var writer = XmlWriter.Create(ms))
                    {
                        feed.SaveAsRss20(writer);
                        writer.Flush();
                        var buffer = ms.ToArray();
                        var output = Encoding.UTF8.GetString(buffer, 0, buffer.Length);

                        response = Request.CreateResponse();
                        response.Content = 
                            new StringContent(output, Encoding.UTF8, 
                                "application/rss+xml");
                    }
                }
            }
        }
        catch (Exception ex)
        {
            response = 
                Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
        }

        return response;
    }
}

Note that, in order to use the SyndicationFeed and SyndicationItem classes, we must add a reference to the System.ServiceModel.dll assembly.

Now we can create a Windows Store app that calls the RequestSetImageFeedAsync method:

var result = await LockScreen.RequestSetImageFeedAsync(
    new Uri("http://mypersonalsite.azurewebsites.net/api/wallpapers"));

if (result == SetImageFeedResult.Success)
{
    // The image feed was set was successfull.
}
else if (result == SetImageFeedResult.ChangeDisabled)
{
    // The feed was not set because the lock screen image slide show 
    // is disabled by group policy.
}
else if (result == SetImageFeedResult.UserCanceled)
{
    // The operation was canceled by the user.
}

When executing the method at line 1, Windows will show a message to confirm the operation. If we decide to continue, the feed will be automatically saved, and the app will be listed in the PC and devices | Lock screen section of the PC Settings app. Note that, in some cases, to correctly show the images, we must turn off the switch Only show the pictures that will fit best on my screen (the screenshot below is taken from the Italian version of Windows 8.1):

Windows 8.1 Lock screen settings

Usually, we must wait at least one minute before the feed images will start to be used. After this, the RSS will be regularly updated, so that our slideshow wil always show the most recent Bing images.

The feed will be available as long as our app is installed in the system. If we want to stop the slideshow from our app, we need to call the LockScreen.TryRemoveImageFeed.

Categories: ASP.NET WebAPI, C#, WinRT