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.