The actual implementation of the web service is very straightforward, as the following code demonstrates:
/// <summary>
/// WCF Rest service for the localization "resources" resource.
/// </summary>
[ServiceBehavior(IncludeExceptionDetailInFaults = true, InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class LocalizationResources : ILocalizationResources
{
/// <summary>
/// Gets the collection of <see cref="ResourceEntry"/> in JSON format.
/// </summary>
/// <param name="cultureName">Name of the culture for which the <see cref="CollectionService{ResourceEnty}"/> should be retrieved.</param>
/// <param name="classId">The id of the class for which the <see cref="CollectionService{ResourceEnty}"/> should be retrieved.</param>
/// <param name="provider">The name of the resource provider from which the resources should be retrived.</param>
/// <param name="sort">The sort expression used to order the retrieved resources.</param>
/// <param name="skip">The number of resources to skip before populating the collection (used primarily for paging).</param>
/// <param name="take">The maximum number of resources to take in the collection (used primarily for paging).</param>
/// <param name="filter">The filter expression in dynamic LINQ format used to filter the retrieved resources.</param>
/// <returns>
/// <see cref="CollectionContext{ResourceEnty}"/> object with resource entry items and other information about the retrieved collection.
/// </returns>
public CollectionContext<ResourceEntry> GetResources(string cultureName, string classId, string provider, string sort, int skip, int take, string filter)
{
return this.GetResourcesInternal(cultureName, classId, provider, sort, skip, take, filter);
}
/// <summary>
/// Gets the collection of <see cref="ResourceEntry"/> in XML format.
/// </summary>
/// <param name="cultureName">Name of the culture for which the <see cref="CollectionService{ResourceEnty}"/> should be retrieved.</param>
/// <param name="classId">The id of the class for which the <see cref="CollectionService{ResourceEnty}"/> should be retrieved.</param>
/// <param name="provider">The name of the resource provider from which the resources should be retrived.</param>
/// <param name="sort">The sort expression used to order the retrieved resources.</param>
/// <param name="skip">The number of resources to skip before populating the collection (used primarily for paging).</param>
/// <param name="take">The maximum number of resources to take in the collection (used primarily for paging).</param>
/// <param name="filter">The filter expression in dynamic LINQ format used to filter the retrieved resources.</param>
/// <returns>
/// <see cref="CollectionContext{ResourceEnty}"/> object with resource entry items and other information about the retrieved collection.
/// </returns>
public CollectionContext<ResourceEntry> GetResourcesInXml(string cultureName, string classId, string provider, string sort, int skip, int take, string filter)
{
return this.GetResourcesInternal(cultureName, classId, provider, sort, skip, take, filter);
}
/// <summary>
/// Gets the single resource entry in JSON format.
/// </summary>
/// <param name="cultureName">Name of the culture to which the resource is defined for.</param>
/// <param name="classId">The id of the class to which the resource belongs to.</param>
/// <param name="key">The key of the resource.</param>
/// <param name="provider">The name of the resource provider from which the <see cref="ResourceEntry"/> should be retrieved.</param>
/// <returns>ResourceEntry object.</returns>
public ResourceEntry GetResource(string cultureName, string classId, string key, string provider)
{
return this.GetSingleResource(cultureName, classId, key, provider);
}
/// <summary>
/// Gets the single resource entry in XML format.
/// </summary>
/// <param name="cultureName">Name of the culture to which the resource is defined for.</param>
/// <param name="classId">The id of the class to which the resource belongs to.</param>
/// <param name="key">The key of the resource.</param>
/// <param name="provider">The name of the resource provider from which the <see cref="ResourceEntry"/> should be retrieved.</param>
/// <returns>ResourceEntry object.</returns>
public ResourceEntry GetResourceInXml(string cultureName, string classId, string key, string provider)
{
return this.GetSingleResource(cultureName, classId, key, provider);
}
/// <summary>
/// Saves the resource and returns the saved version of the resources in JSON format.
/// </summary>
/// <param name="propertyBag">The array of ResourceEntry properties that should be persisted. The first array contains
/// properties, while the second array holds property name in its first dimension and
/// property value in its second dimension.</param>
/// <param name="cultureName">Name of the culture for which the resource should be saved.</param>
/// <param name="classId">The id of the class for which the resource should be saved.</param>
/// <param name="key">The key of the resource for which the resource should be saved.</param>
/// <param name="provider">The name of the resource provider on which the <see cref="ResourceEntry"/> should be saved.</param>
/// <returns>
/// Newly created or updated <see cref="ResourceEntry"/> object in JSON format.
/// </returns>
/// <remarks>
/// If the resource to be saved does not exist, new resource will be created. If the resource,
/// however, does exist the existing resource will be update.
/// </remarks>
public ResourceEntry SaveResource(string[][] propertyBag, string cultureName, string classId, string key, string provider)
{
return this.SaveAndReturnResource(propertyBag, cultureName, classId, key, provider);
}
/// <summary>
/// Saves the resource and returns the saved version of the resources in XML format.
/// </summary>
/// <param name="propertyBag">The array of ResourceEntry properties that should be persisted. The first array contains
/// properties, while the second array holds property name in its first dimension and
/// property value in its second dimension.</param>
/// <param name="cultureName">Name of the culture for which the resource should be saved.</param>
/// <param name="classId">The id of the class for which the resource should be saved.</param>
/// <param name="key">The key of the resource for which the resource should be saved.</param>
/// <param name="provider">The name of the resource provider on which the <see cref="ResourceEntry"/> should be saved.</param>
/// <returns>
/// Newly created or updated ResourceEntry object in XML format.
/// </returns>
/// <remarks>
/// If the resource to be saved does not exist, new resource will be created. If the resource,
/// however, does exist the existing resource will be update.
/// </remarks>
public ResourceEntry SaveResourceInXml(string[][] propertyBag, string cultureName, string classId, string key, string provider)
{
return this.SaveAndReturnResource(propertyBag, cultureName, classId, key, provider);
}
/// <summary>
/// Deletes the resource entry.
/// </summary>
/// <param name="cultureName">Name of the culture.</param>
/// <param name="classId">The class id.</param>
/// <param name="key">The key.</param>
/// <param name="provider">The name of the resource provider from which the resource entry should be deleted.</param>
public void DeleteResource(string cultureName, string classId, string key, string provider)
{
this.DeleteSingleResource(cultureName, classId, key, provider);
}
#region Private methods
private CollectionContext<ResourceEntry> GetResourcesInternal(string cultureName, string classId, string provider, string sort, int skip, int take, string filter)
{
var manager = Res.GetManager(provider);
CultureInfo cultureInfo = GetCultureInfo(cultureName);
var query = from resoruce in manager.GetResources(cultureInfo)
where resoruce.ClassId == classId
select resoruce;
// extender takes care of it.
if (!string.IsNullOrEmpty(sort))
query = query.OrderBy(sort);
// this is where the quer the query is executed.
int totalCount = query.Count();
// wont fire the process again, when marked as IEnumerable.
var items = query.AsEnumerable().Skip(skip).Take(take);
var collectionContext = new CollectionContext<ResourceEntry>(items)
{
TotalCount = totalCount
};
return collectionContext;
}
private ResourceEntry SaveAndReturnResource(string[][] propertyBag, string cultureName, string classId, string key, string provider)
{
var properties = new Dictionary<string, string>();
for (int i = 0; i < propertyBag.GetLength(0); i++)
{
if (properties.ContainsKey(propertyBag[i][0]))
throw new WebProtocolException(HttpStatusCode.InternalServerError, "ERROR: The property bag contains duplicate property '{0}', which is not allowed.".Arrange(propertyBag[i][0]), null);
properties.Add(propertyBag[i][0], propertyBag[i][1]);
}
var manager = Res.GetManager(provider);
CultureInfo culture = GetCultureInfo(cultureName);
if (string.IsNullOrEmpty(classId))
classId = properties["ClassId"];
if (string.IsNullOrEmpty(key))
key = properties["Key"];
var resourceEntry = manager.GetResources(culture, classId).Where("Key = \"" + key + "\"").SingleOrDefault() ??
manager.AddItem(culture, classId, key, string.Empty, string.Empty);
if(properties.ContainsKey("Value"))
resourceEntry.Value = properties["Value"];
if(properties.ContainsKey("Description"))
resourceEntry.Description = properties["Description"];
try
{
manager.SaveChanges();
}
catch (Exception ex)
{
throw new WebProtocolException(HttpStatusCode.InternalServerError, "ERROR: Item could not have been saved.", ex.InnerException);
}
return resourceEntry;
}
private ResourceEntry GetSingleResource(string cultureName, string classId, string key, string provider)
{
var manager = Res.GetManager(provider);
CultureInfo cultureInfo = GetCultureInfo(cultureName);
IQueryable<ResourceEntry> filter1 = manager.GetResources(cultureInfo, classId);
ResourceEntry entry1 = filter1.Where("Key = \"" + key + "\"").SingleOrDefault();
return entry1;
}
private void DeleteSingleResource(string cultureName, string classId, string key, string provider)
{
var manager = Res.GetManager(provider);
CultureInfo cultureInfo = (string.IsNullOrEmpty(cultureName)) ? CultureInfo.InvariantCulture : new CultureInfo(cultureName);
manager.DeleteItem(cultureInfo, classId, key);
}
private CultureInfo GetCultureInfo(string cultureName)
{
CultureInfo cultureInfo;
if (string.IsNullOrEmpty(cultureName))
cultureInfo = CultureInfo.InvariantCulture;
else if (cultureName.ToUpperInvariant() == "INVARIANT")
cultureInfo = CultureInfo.InvariantCulture;
else
cultureInfo = new CultureInfo(cultureName);
return cultureInfo;
}
#endregion
All that needs to be done is implement the members mandated by the interface, and perform the expected task by relying on the manager classes (server side API) to do the actual work.
Often it makes sense to encapsulate the actual methods that perform the work in separate methods and only call them from interface mandated members (for example, you will need a same logic for both XML and JSON responses).
Additionally, it is very important to be very careful about exception handling in the web service implementations, since the problematic code will not actually show a problem to the user, unless you specifically throw a WebProtocolException.
Other articles in this topic will explain the specifics of CollectionContext object, serialization and exception handling.