Mike Taulty's Blog
Bits and Bytes from Microsoft UK
Silverlight: Product Maintenance Application ( Part 2 – Building some web services )

Blogs

Mike Taulty's Blog

Elsewhere

Following up from this post, I set about building some services to power my application’s data requirements. These aren’t perfect by any means but they’re enough for a sample application to get going with. Working from the sketch of how the services need to look given at the end of the previous post, I put together a LINQ to SQL diagram;

image

and left LINQ to SQL to do concurrency checking based on every column in the Product table ( I only plan to modify the Product table ) which is its default if I remember correctly ( and, usually, overkill but it’ll do for me here ).

And I then produced a rough service interface;

public class LookupDataItem
{
public int Id { get; set; }
public string Title { get; set; }
}

public class ProductSales
{
public string MonthYearLabel { get; set; }
public decimal TotalSales { get; set; }
}

public class ProductSearchResult
{
public int Page { get; set; }
public int TotalPages { get; set; }
public List<Product> Products { get; set; }
}

public class ProductChangeEntry
{
public Product OriginalProduct { get; set; }
public Product CurrentProduct { get; set; }
}

[ServiceContract]
public interface IProductService
{
[OperationContract]
Dictionary<string, List<LookupDataItem>> GetReferenceData();

[OperationContract]
ProductSearchResult GetProducts(int pageNumber, int pageSize, string matchString);

[OperationContract]
List<ProductSales> GetProductSales(int productId);

[OperationContract]
List<int> UpdateProducts(List<ProductChangeEntry> entries);

[OperationContract]
void InsertProducts(List<Product> products);
}

it’s not particularly beautiful but it’ll mostly get done what I want to get done. The way I see GetReferenceData working is that it’ll return to me a bunch of lookup values – i.e. a dictionary like;

“categories” : { 0, “food” }, { 1, “drink” }, { 2, “eggs” }

“suppliers”: { 0, “Supplier 1” }, { 1, “Supplier 2” }

and I can use that to populate ComboBoxes in the UI.

The odd-looking return value on UpdateProducts is intended to cope with the idea that we won’t always be able to successfully make all the changes requested of us and so we return a list of which updates actually worked – i.e. which IDs we did successful work on so that a caller can try and determine how successful/unsuccessful we were in making the updates that they asked for.

I don’t intend to try and do any “clever” conflict resolution on the server-side, I’ll just let the list of succeeded entries go back to the client and let the client figure it out ( which will be equally “unclever” in my case :-) ).

I made sure that my service was configured over HTTPS ( NB: ASP.NET role and membership services snipped from config file below );

<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior name="ServiceBehavior">
        <serviceAuthorization principalPermissionMode="UseAspNetRoles">
          <authorizationPolicies>
            <add policyType="AuthPolicy, AuthBits"/>
          </authorizationPolicies>
        </serviceAuthorization>
        <serviceMetadata httpGetEnabled="true"/>
        <serviceDebug includeExceptionDetailInFaults="false"/>
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <bindings>
    <basicHttpBinding>
      <binding name="bindingConfig">
        <security mode="Transport"/>
      </binding>
    </basicHttpBinding>
  </bindings>
  <services>

    <service behaviorConfiguration="ServiceBehavior"
             name="ProductService">
      <endpoint address=""
                binding="basicHttpBinding"
                contract="IProductService"
                bindingConfiguration="bindingConfig"/>
    </service>

    </service>
  </services>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
</system.serviceModel>

and then wrote a basic implementation of that service interface;

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class ProductService : IProductService
{
[PrincipalPermission(SecurityAction.Demand, Role = "users")]
public Dictionary<string, List<LookupDataItem>> GetReferenceData()
{
// We could/should maybe cache this but it'd be app dependent as to
// whether that'd make sense or not.
Dictionary<string, List<LookupDataItem>> data = new Dictionary<string, List<LookupDataItem>>();

using (NorthwindDataDataContext ctx = new NorthwindDataDataContext())
{
data.Add("suppliers",
(
from s in ctx.Suppliers
select new LookupDataItem()
{
Id = s.SupplierID,
Title = s.CompanyName
}
).ToList());

data.Add("categories",
(
from c in ctx.Categories
select new LookupDataItem()
{
Id = c.CategoryID,
Title = c.CategoryName
}
).ToList());
}
return (data);
}

[PrincipalPermission(SecurityAction.Demand, Role = "users")]
public ProductSearchResult GetProducts(int pageNumber, int pageSize, string matchString)
{
ProductSearchResult result = null;

using (NorthwindDataDataContext ctx = new NorthwindDataDataContext())
{
ctx.DeferredLoadingEnabled = false;

var query =
(
from p in ctx.Products
select p
);

if (!string.IsNullOrEmpty(matchString))
{
query = from p in query
where p.ProductName.ToLower().Contains(matchString.ToLower())
select p;
}
int totalRecords = query.Count(); // Round trip #1.

query =
(
from p in query
orderby p.ProductID ascending
select p
).Skip(pageNumber * pageSize).Take(pageSize);

result = new ProductSearchResult()
{
Page = pageNumber,
TotalPages = (int)Math.Ceiling(totalRecords / (double)pageSize),
Products = query.ToList() // Round trip #2.
};
}
return (result);
}

[PrincipalPermission(SecurityAction.Demand, Role = "users")]
[PrincipalPermission(SecurityAction.Demand, Role = "viewers")]
public List<ProductSales> GetProductSales(int productId)
{
List<ProductSales> sales = null;

using (NorthwindDataDataContext ctx = new NorthwindDataDataContext())
{
var query =
from od in ctx.Order_Details
where od.ProductID == productId
group od by new
{
Month = od.Order.OrderDate.Value.Month,
Year = od.Order.OrderDate.Value.Year
}
into grouped
orderby grouped.Key.Year, grouped.Key.Month ascending
select new ProductSales
{
TotalSales = grouped.Sum(x => (x.Quantity * x.UnitPrice) - (decimal)x.Discount),
MonthYearLabel = string.Format("{0}, {1}",
grouped.Key.Month, grouped.Key.Year)
};

sales = query.ToList();
}
return (sales);
}

[PrincipalPermission(SecurityAction.Demand, Role = "users")]
[PrincipalPermission(SecurityAction.Demand, Role = "editors")]
public List<int> UpdateProducts(List<ProductChangeEntry> entries)
{
List<int> changedIds = new List<int>();

using (NorthwindDataDataContext ctx = new NorthwindDataDataContext())
{
foreach (var item in entries)
{
changedIds.Add(item.CurrentProduct.ProductID);
ctx.Products.Attach(item.CurrentProduct, item.OriginalProduct);
}
try
{
ctx.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException)
{
foreach (var item in ctx.ChangeConflicts)
{
changedIds.Remove(((Product)item.Object).ProductID);

item.Resolve(RefreshMode.OverwriteCurrentValues);
}
try
{
ctx.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch
{
changedIds = null;
}
}
catch
{
changedIds = null;
}
}
return (changedIds);
}

[PrincipalPermission(SecurityAction.Demand, Role = "users")]
[PrincipalPermission(SecurityAction.Demand, Role = "editors")]
public void InsertProducts(List<Product> products)
{
// TODO: Not doing much with errors here right now.
using (NorthwindDataDataContext ctx = new NorthwindDataDataContext())
{
ctx.Products.InsertAllOnSubmit(products);

try
{
ctx.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch
{
// TODO: Do something sensible here, alter return value.
}
}
}
}

Ok, now I’ve got some services to work against, I can build a data library that makes use of those services in order to allow the controls that I want to build to data-bind.

Next post – building some data classes on the client side…


Posted Sun, Jun 14 2009 3:52 AM by mtaulty
Filed under: , ,

Comments

Mike Taulty's Blog wrote Silverlight: Product Maintenance Application ( Part 3 – Getting some data )
on Sun, Jun 14 2009 4:16 AM

Following up on this previous post I wanted to start building out some classes that managed data for

Silverlight Product Maintenance Application wrote Silverlight Product Maintenance Application
on Mon, Jun 15 2009 4:21 AM
NewsPeeps wrote Silverlight: Product Maintenance Application ( Part 2 – Building some web services ) - Mike Taulty's Blog - Mike Taulty's Blog
on Sat, Jul 11 2009 6:15 PM

Thank you for submitting this cool story - Trackback from NewsPeeps

Mike Taulty's Blog wrote Linked In Talk - “Beyond Silverlight with WPF”
on Wed, Oct 7 2009 2:12 PM

If you came along to the talk that I did for the Linked In .NET User Group today via Live Meeting then