Web API: Supporting data shaping


Usually while building high availability Web API’s, where you know that typically your business objects are quite complex with a lot of properties returned as a part of the object, to the client, one would ideally like to give the client the ability to be able to request a specific number of fields.

That’s understandable from the business point of view and also giving the client a little more control over what they want to get from the API. Although, from the technical side of things, it does pose a few questions:

  1. How do you want to get the fields requested from the client
  2. How to you manage the scenarios where the client requested some navigation properties (and only specific fields within the navigation property)
  3. How to structure the result returned

I am going to try to address this functionality and these points through an example and for the sake of brevity my objects will be a lot simpler to demonstrate the use case in question.

Lets say we have two objects called Trip and Stop, that are defined as:

public class Trip
{
     public int Id { get; set; }
     public string Name { get; set; }
     public string Description { get; set; }
     public DateTime StartDate { get; set; }
     public DateTime? EndDate { get; set; }
     public virtual ICollection<Stop> Stops { get; set; }
}

public class Stop
{
	public int Id { get; set; }
	public string Name { get; set; }
	public DateTime ArrivalDate { get; set; }
	public DateTime? DepartureDate { get; set; }
	public decimal Latitude { get; set; }
	public decimal Longitude { get; set; }

	public virtual int TripId { get; set; }
	public virtual Trip Trip { get; set; }
}

And you have a REST endpoint that implements [HTTPGET] and returns a list of trips. Now the user might only be interested in getting the Name and a list of Stops within a trip for all the trips that are returned. So we need to tell the API the fields that the user wants to request.
Below is one way that this scenario can be addressed.

[HttpGet]
public IHttpActionResult Get(string fields="all")
{
	try
	{
		var results = _tripRepository.Get();
		if (results == null)
			return NotFound();
		// Getting the fields is an expensive operation, so the default is all,
		// in which case we will just return the results
		if (!string.Equals(fields, "all", StringComparison.OrdinalIgnoreCase))
		{
			var shapedResults = results.Select(x => GetShapedObject(x, fields));
			return Ok(shapedResults);
		}
		return Ok(results);
	}
	catch (Exception)
	{
		return InternalServerError();
	}
}

public object GetShapedObject<TParameter>(TParameter entity, string fields)
{
	if (string.IsNullOrEmpty(fields))
		return entity;
	Regex regex = new Regex(@"[^,()]+(\([^()]*\))?");
	var requestedFields = regex.Matches(fields).Cast<Match>().Select(m => m.Value).Distinct();
	ExpandoObject expando = new ExpandoObject();

	foreach (var field in requestedFields)
	{
		if (field.Contains("("))
		{
			var navField = field.Substring(0, field.IndexOf('('));

			IList navFieldValue = entity.GetType()
										?.GetProperty(navField, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public)
										?.GetValue(entity, null) as IList;
			var regexMatch = Regex.Matches(field, @"\((.+?)\)");
			if (regexMatch?.Count > 0)
			{
				var propertiesString = regexMatch[0].Value?.Replace("(", string.Empty).Replace(")", string.Empty);
				if (!string.IsNullOrEmpty(propertiesString))
				{
					string[] navigationObjectProperties = propertiesString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

					List<object> list = new List<object>();
					foreach (var item in navFieldValue)
					{
						list.Add(GetShapedObject(item, navigationObjectProperties));
					}

					((IDictionary<string, object>)expando).Add(navField, list);
				}
			}
		}
		else
		{
			var value = entity.GetType()
							  ?.GetProperty(field, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public)
							  ?.GetValue(entity, null);
			((IDictionary<string, object>)expando).Add(field, value);
		}
	}

	return expando;
}

///
<summary>
/// Creates an object with only the requested properties by the client
/// </summary>

/// <typeparam name="TParameter">Type of the result</typeparam>
/// <param name="entity">Original entity to get requested properties from</param>
/// <param name="fields">List of properties requested from the entity</param>
/// <returns>Dynamic object as result</returns>
private object GetShapedObject<TParameter>(TParameter entity, IEnumerable<string> fields)
{
	ExpandoObject expando = new ExpandoObject();
	foreach (var field in fields)
	{
		var value = entity.GetType()
						  ?.GetProperty(field, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)
						  ?.GetValue(entity, null);
		((IDictionary<string, object>)expando).Add(field, value);
	}
	return expando;
}

So, this allows the user to pass in the query string, a comma separated list of strings that specifies the names of the fields that he wants to be returned such as:

http://localhost:2365/api/trips?fields=name,stops(name,latitude,longitude)

and that would just contain the requested fields (thanks to ExpandoObject class) that helps us construct the object and return the results back to the client as below:

{
	"totalCount": 2,
	"resultCount": 2,
	"results": [
		{
			"name": "Trip to Scandanavia",
			"stops": [
				{
					"name": "Denmark",
					"latitude": 73.2323,
					"longitude": 43.2323
				}
			]
		},
		{
			"name": "Trip to Europe",
			"stops": [
				{
					"name": "Germany",
					"latitude": 72.37657,
					"longitude": 42.37673
				},
				{
					"name": "France",
					"latitude": 72.22323,
					"longitude": 42.3434
				}
			]
		}
	]
}

And that’s all. You can of course build on this approach and add support for multiple nested navigation fields support. Happy coding!

Advertisements

Restrict an Administrator to create new users as per his allocated quota in a Multi-Tenant environment


Lately I have been working on a Multi-Tenant application using the latest MVC5 framework. The application is essentially an Ecommerce reporting and product analysis and forecasting application that can pull clients stock and sales information from different ecommerce channels like Amazon, Ebay, Groupon and Magento

Here is what the primary technology stack I’ve used in the application looks like:

  • Asp.Net MVC5
  • Asp.Net Identity Framework 2.0.1
  • Entity Framework 6.0.1
  • SQL Server 2008
  • Html5, CSS3, JQuery

So, as with any other Multi-Tenant application the application had various Subscription Levels or packages. Each Tenant can subscribe to any one Subscription Package which had a number of features. One of the features was the number of User Accounts the Tenant Administrator could create. This would vary depending on his subscription level. The Tenant Owner could request to add more user accounts to his subscription by paying a cost per user, and then that would be the user accounts limit.

So, basically there was a need for a Gated User creation process based on the Subscription Level of the Tenant and any additional users he had requested. I thought about a few approaches like:

  • Storing the Number of users allowed in the Session for a Tenant and then checking it each time I would create a user
    • The problem with this approach was that there were other features as well who’s creating was to be gated. So essentially I would need to store every feature in the session. Or store a list of features in the session. And then in the case of Users, when an additional user was requested then I would have to updated that value in the session variable as well along with updating the database. A bit that I didn’t wanted to manage
  • Setup a role for the ability to create users and remove it when they have created too many. Whenever they create a user, check to see if they meet the threshold, if they do, remove this privilege from their account.
    • This method was too cumbersome. Imagine if the admin removes a user, then if the role “CanCreateUsers” is not present, then we first add it, then do the whole process of adding entity, then if the limit reaches, then we remove this role again. And we do that on every User add and remove, plus also when an Owner requested additional user to the account. Its too much overhead and invitation to bugs

So after thinking about a few approaches I ended up using a mix of the ActionMethodSelectorAttribute and a simple HtmlHelperExtension along with an enum that described my gated actions. The code is as follows:

GatedAction enum

public enum GatedAction
{
    CreateUser,
    CreateAnotherFeature,
    ...
}

ActionMethodSelectorAttribute – SubscriptionActionAttribute

public class SubscriptionActionAttribute: ActionMethodSelectorAttribute
{
    public GatedAction Action { get; set; }

    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
    {
        var tenantId = controllerContext.RouteData.GetTenantId();
        using (var context = new ApplicationDbContext())
        {
            var tenant = context.Set<Tenant>().Include("Subscription").FirstOrDefault(t => t.Id == tenantId);
            switch (Action)
            {
                case GatedAction.CreateUser:
                    var totalAllowedUsers = tenant.AdditionalUsersRequested + tenant.Subscription.UsersAllowed;
                    var existingUsersCount = tenant.Users.Count();
                    return (existingUsersCount <= totalAllowedUsers);
                ...
                default:
                    return false;
            }
        }
        return false;
    }
}

GatedActionLink – HtmlHelper extension

public static MvcHtmlString GatedActionLink(this HtmlHelper helper,
    string icon,
    string text,
    string actionName,
    string controllerName,
    GatedAction action,
    RouteValueDictionary routeValues = null,
    IDictionary<string, object> htmlAttributes = null)
{
    ControllerBase controllerBase = string.IsNullOrEmpty(controllerName) ? helper.ViewContext.Controller : helper.GetControllerByName(controllerName);
	ControllerContext controllerContext = new ControllerContext(helper.ViewContext.RequestContext, controllerBase);
    ControllerDescriptor controllerDescriptor = new ReflectedControllerDescriptor(controllerContext.Controller.GetType());
    ActionDescriptor actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);

    htmlAttributes = htmlAttributes ?? new Dictionary<string, object>();
    if (actionDescriptor == null)
        htmlAttributes.Add("disabled", "disabled");            

    return helper.ActionLinkWithIcon(icon, text, actionName, controllerName, routeValues, htmlAttributes);
}
</pre>

And that’s it really. Now all I have to do is to decorate my action method like this:

[HttpGet]
[SubscriptionAction(Action = GatedAction.CreateUser)]
public async Task CreateUser()
{
...
}

and in my view the create action link would be like:

@Html.GatedActionLink("glyphicon glyphicon-plus", "create user", "CreateUser", "Manage", GatedAction.CreateUser, new RouteValueDictionary { { "area", "Users" } }, new Dictionary { { "class", "btn btn-default btn-xs" } })

And that’s all. We are sorted. I only update my database and rest is handled by my custom filter attribute and the html helper extension.

Online Catalog: My first Git published on Git-hub


Recently been working on getting an end to end implementation of an example that would use ASP.Net MVC4, Twitter Bootstrap and a lot other Microsoft and other open source technologies as a shop window and showcase of these products being used together in harmony.
So here I am with the first check in for “Online Catalog”, an online product catalog with integrated Admin section and shopping cart along with a full order management and reporting functionality.

Please feel free to drop in your comments and or any suggested changes. I will be updating this repository in the time to come with newer functionality being added until its marked complete.

Watch out this space for updated and new code availability!!
https://github.com/JinishBhardwaj/Online-Catalog