Google API for .NET architecture (Part 2)
Part 1: Google API for .NET architecture (Part 1)
Today is Harvey Nash - 10 years in Viet Nam celebration day. I very happy about that, and I also drunk some beer, so I have been eager to write this post. In last post, I had been analyze about Domain Model of Gapi4net, some of people maybe like it. That is a reason I write this post in next. And what will I write in this post? As you knew in last post, I will be analyze about some technical I used in Gapi4net. Three of techniques that I will present is download data, build the Url and make Fluent Interface for Gapi4net.
+ Download data: I use a technical that post from this link for implemented the asynchronous request to Google services. I want to execute it in asynchronous because it will save a little bit time when request to Google services, and I will continue to process something else until the request to Google services are completed. I thought I will not explain it clearly, because everyone also knew why we use the asynchronous request. Next step, I only need to code the Downloader, this code will be show as below:
public interface IDownloader : IDisposable
{
void Add(string url);
int Count { get; }
IEnumerable<IAsync> DownloadAll(Action<Async<string>> action);
}
internal class Downloader : IDownloader
{
private readonly IList<string> _urls;
public Downloader()
{
_urls = new List<string>();
}
public void Add(string url)
{
Contract.Assert(!string.IsNullOrEmpty(url), "Url is null or empty");
_urls.Add(url);
}
public IEnumerable<IAsync> DownloadAll(Action<Async<string>> action)
{
return _urls.Select(url => Async.Parallel(
ProcessResultFromDownloadContentFromUrl(url, action)
));
}
private IEnumerable<IAsync> ProcessResultFromDownloadContentFromUrl(string url, Action<Async<string>> action)
{
var req = WebRequest.Create(url);
// asynchronously get the response from http server
var response = req.GetResponseAsync();
yield return response;
var resp = response.Result.GetResponseStream();
// download HTML using the asynchronous extension method
// instead of using synchronous StreamReader
var html = resp.ReadToEndAsync().ExecuteAsync<string>();
yield return html;
action(html);
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
public int Count
{
get { return _urls.Count; }
}
}
+ Build the Url: Because my request is a Url, so I must to build the Url to request to Google services. I will convert from the input entities to Url, so I must to reflection the entities for get metadata and use it to build Url. To be clearly this, I will show one sample, assume that I have a input entity as:
public abstract class EntityBase : IEntity
{
public string Query { get; set; }
public string Version { get; set; }
public string UserIp { get; set; }
public int ResultSize { get; set; }
public string HostLanguage { get; set; }
public string Key { get; set; }
public int Start { get; set; }
public abstract string SearchWebUrl();
}
public class Web : EntityBase, IWeb
{
public string UniqueId { get; set; }
public string Linked { get; set; }
// Safe type
public Safe Safe { get; set; }
// ParticularLanguage type
public ParticularLanguage ParticularLanguage { get; set; }
// Filter type
public Filter Filter { get; set; }
// CountryCode type
public CountryCode CountryCode { get; set; }
}
It is a web's entity, so we need knowing what are the fields that we need to build? To do that, we need creating a custom attribute as:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property,
AllowMultiple = false,
Inherited = true)]
public class UrlElementAttribute : Attribute
{
public string Name { get; private set; }
public bool Required { get; private set; }
public UrlElementAttribute(string name)
{
Name = name;
Required = true;
}
}
After that I will decorate this custom attribute on my web's entity as:
public abstract class EntityBase : IEntity
{
[UrlElement("q")]
public string Query { get; set; }
[UrlElement("v")]
public string Version { get; set; }
public string UserIp { get; set; }
public int ResultSize { get; set; }
public string HostLanguage { get; set; }
public string Key { get; set; }
[UrlElement("start")]
public int Start { get; set; }
public abstract string SearchWebUrl();
}
Finally, I will write code for exploring this entity as:
public interface IUrlBuilder<T> where T : IEntity
{
string BuildUrl();
T Entity { get; set; }
}
public abstract class UrlBuilderBase<T> : IUrlBuilder<T> where T : IEntity
{
public T Entity { get; set; }
public virtual string BuildUrl()
{
var builder = new StringBuilder();
var propertyInfos = Entity.GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var info in propertyInfos)
{
var attrs = info.GetCustomAttributes(typeof(UrlElementAttribute), true);
if (attrs.Length == 0)
{
continue;
}
var urlAttr = attrs[0] as UrlElementAttribute;
builder.AppendFormat("{0}={1}&", urlAttr.Name, info.GetValue(Entity, null));
}
return builder.ToString().Substring(0, builder.ToString().Length - 1);
}
}
public class WebUrlBuilder : UrlBuilderBase<Web>
{
}
All thing is fine, and now we can convert from the entities to Url with a little bit code. Are you like that too?
+ Make Fluent Interface: As you knew, Gapi4net is written for easy used, so I implemented the Fluent Interface inside it and applied the Lambda Expression to association with Fluent Interface. It make the cleanly structure for invoked the APIs as:
Gapi4NetFactory<T>
.Init()
.With(x => x.Version, "1.0")
.With(x => x.Query, searchText)
.Create();
I copied some of code from this link, and modified something to fixed with my solution. It is so cool! Below is full of this code:
public interface IGenericFactory<T>
{
IGenericFactory<T> With(Expression<Func<T, object>> property, object value);
T Create();
}
public class GenericFactory<T> : IGenericFactory<T>
{
private T _entity;
public GenericFactory(T entity)
{
_entity = entity;
}
public IGenericFactory<T> With(Expression<Func<T, object>> property, object value)
{
PropertyInfo propertyInfo = null;
if (property.Body is MemberExpression)
{
propertyInfo = ((MemberExpression)property.Body).Member as PropertyInfo;
}
else
{
propertyInfo = ((MemberExpression)((UnaryExpression)property.Body).Operand).Member as PropertyInfo;
}
propertyInfo.SetValue(_entity, value, null);
return this;
}
public T Create()
{
return _entity;
}
}public static class Gapi4NetFactory<T> where T : IEntity, new()
{
public static IGenericFactory<T> Init()
{
return new GenericFactory<T>(new T());
}
}
That is the main techniques I used in Gapi4net. For more information please download my library and look it clearly. In next post, I will show you some of technical in ASP.NET MVC 3 Preview 1. Hope you will welcome it. Thanks for you read this post and see you next time.