.Net RIA Services Metadata Programming Model
- 15
- Add a Comment
Welcome Back! I hope you enjoy the content on this site. If you have not done so already, you may want to subscribe to my RSS feed or become a fan of this blog on Facebook. Thanks for visiting!
This is the third part of the a multi post series on .Net RIA Services.
First Part - Getting Started with .Net RIA Services
Second Part - .Net RIA Services – Hello World
In this part I will talk about using the .Net RIA Services Metadata Model for data validation and authorization. Unlike the second part of this series this won’t be a tutorial but more of a theoretical article.
We have all suffered when designing business applications to implement data validation and authorization. It’s a dull and meticulous process that bores the hell out of any developer.
Proper data validation should be implemented at two levels, at the UI with ASP.Net validators for example and at the business tier. What if we could write user input validation and data validation code only once in such a way that it’s used both at the UI level and in the business tier, and lets push it further, what if we could write it such that no matter which client (Presentation Layer) we use it will be taken into consideration. Sounds pretty optimistic? Actually it’s not, this is exactly what .Net RIA Services Metadata programming model can do for you.
We would start by defining our Entity class, the Metadata class and then a DomainService to enable client access from the Silverlight application so we can take a look at how the validation Metadata is translated on the client side.
Defining the Entity and the Metadata Class
Let’s take a look at some code. Say you are writing a inventory system and have a product class defined like this:
public class Product { public string Name { get; set; } public string SKU { get; set; } public double Price { get; set; } }
We surely want to have some validation when the user inputs this values. So we want:
- All three properties to be required
- The SKU to have a length of 8 characters.
- The Price to be greater than zero
- Define a Key for the Entity
We would start by creating a Metadata class for our Product class we will call it ProductMetadata.
Metadata classes provide a convenient way to attach metadata to an entity without actually modifying the corresponding members on the entity itself.
The rules for using a metadata class are:
- The entity class must be marked with [MetadataType] to name the metadata class
- The metadata class must contain only members whose name match members in the entity class.
The metadata class may omit members it does not want to extend, but it may not contain members not in the entity class. The members are required only to match in name, which explains why Name is a property in the Product class but a field in the metadata class.
Metadata members are not required to be of the same type as their corresponding entity member, but it helps readability if they are.
For every member of the entity class that has a matching member in the metadata class, the custom attributes are combined. The metadata class exists only on the server. When code-generation creates the client proxy class for the entity, the custom attributes are combined into the the generated class.
Bellow is the product class with the Metadata class:
[MetadataType(typeof(ProductMetadata))] public class Product { public string Name { get; set; } public string SKU { get; set; } public double Price { get; set; } internal sealed class ProductMetadata { public string Name; public string SKU; public double Price; } }
Notice the attribute we added to the Product class that sets which class we would use for the Metadata:
[MetadataType(typeof(ProductMetadata))]
Making the Properties Required
To make the properties required all we need to do is add the validation attribute RequiredAttribute available in System.ComponentModel.DataAnnotations. The class should now look like this:
internal sealed class ProductMetadata { [Required] public string Name; [Required] public string SKU; [Required] public double Price; }
Setting the SKU Length
We use the StringLengthAttribute also available in System.ComponentModel.DataAnnotations. This attribute is used to assert a field, parameter or property does not exceed a defined length.
[Required] [StringLength(8,MinimumLength=8)] public string SKU;
Validate the price
We use the RangeAttribute also available in System.ComponentModel.DataAnnotations. This attribute is used for specifying a range constraint.
[Required] [Range(0, double.MaxValue)] public double Price;
Defining a Key for the Entity
This is pretty simple as well. We can use the SKU as the key for the entity as it should be unique for each product. And for that we use the KeyAttribute which is used to set the unique identifier for an Entity.
[Required] [StringLength(8,MinimumLength=8)] [Key] public string SKU;
Building the Product DataStore
To be able to use our Product class from Silverlight client application we need to create a “fake” DataStore which we would use to retrieve dummy data from.
The store is a simple class which defines a List<T> of products and a getter property to return the list.
public class ProductData { private List<Product> m_products = new List<Product>(new Product[] { new Product() { Name="Mouse", SKU="MOU12345", Price=99.00}, new Product() { Name="Keyboard", SKU="KEY45678", Price=159.00}}); public IList<Product> Products { get { return this.m_products; } } }
Creating a DomainService for the Product class
The DomainService class would reference the Product store and have a method that would return the list of products. The goal here as said earlier is not to explain how the domain service works but to create the necessary parts to take a look at how the Metadata attributes are translated in the generated code.
So here is the code for the ProductService:
[EnableClientAccess()] public class ProductService : DomainService { private ProductData m_productData = new ProductData(); public IEnumerable<Product> GetProducts() { return this.m_productData.Products; } }
Now we have all the parts needed so let’s take a look at the generated code.
The Generated Validation Code
When we open the generated cs file in the Silverlight client application, beside the generated method from the DomainService we also find a translated definition of our Product class:
[DataContract(
Namespace="http://schemas.datacontract.org/2004/07/Demo.HelloWorld.Data_Models")] public sealed partial class Product : Entity { private string _name; private double _price; private string _sku; [DataMember()] [Required()] public string Name { get { return this._name; } set { if ((this._name != value)) { this.ValidateProperty("Name", value); this.RaiseDataMemberChanging("Name"); this._name = value; this.RaiseDataMemberChanged("Name"); } } } [DataMember()] [Range(((double)(0)), ((double)(1.7976931348623157E+308)))] [Required()] public double Price { get { return this._price; } set { if ((this._price != value)) { this.ValidateProperty("Price", value); this.RaiseDataMemberChanging("Price"); this._price = value; this.RaiseDataMemberChanged("Price"); } } } [DataMember()] [Key()] [Required()] [StringLength(8, MinimumLength=8)] public string SKU { get { return this._sku; } set { if ((this._sku != value)) { this.ValidateProperty("SKU", value); this.RaiseDataMemberChanging("SKU"); this._sku = value; this.RaiseDataMemberChanged("SKU"); } } } public override object GetIdentity() { return this._sku; } }
We notice that all our validation and Metadata added is also available on the client side as well as the validation method call:
this.ValidateProperty("Name", value);
This call is what makes it work, before the change of the property’s value it’s validated and a validation error is raised if it doesn’t meet the constraints.
I hope that was a helpful description of how get started using the Metadata Model in .Net RIA Services.
Please Subscribe to my RSS Feed to follow this series on the .Net RIA Services.
Oh and if you liked the article please Kick It bellow!
15 Comments
.Net RIA Service – Hello World | Hatim's Development Blog
April 2nd, 2009
at 9:47am
[...] Update: Check out the next part about using Metadata Programming in .Net RIA Services [...]
[Unit]
April 2nd, 2009
at 10:26am
Nice post!
Right now i’m having a hard time, trying to use RIA in combination with existing LINQ class library. All classes inherit from a base class which has a property, which RIA refuses to serialize, reporting that “Class has a property with an unsupported type”
I was wondering if you knew if it’s possible to use attributes to prevent some business object class property to be serialized to the client POCO class by Ria Services. Something like the XmlIgnore attribute.
Thanks!
Hatim
April 2nd, 2009
at 10:40am
Thanks Unit!
It doesn’t seem like there is a way to exclude a property from a class. There is an [IgnoreOperation] for the domain service, but nothing I could find for now for a property. What’s the Type you having an Issue with?
Hatim
April 2nd, 2009
at 10:45am
Actually I was too quick on that Answer, there is indeed an [Exclude] attribute that you can use and it would ignore that property!
[Unit]
April 2nd, 2009
at 11:20am
Hmm, that’s odd, i’ve never found it. what is the namespace, the [Exclude] attribute is in?
The property is of custom type that inherits from the List. List implements the IList interface, so theoretically it should be supported by RIA.
Thanks for the hint, i’ll give it a shot.
Hatim
April 2nd, 2009
at 11:26am
It’s in System.Web.DomainServices the assembly is System.Web.DomainServices.dll you can find it in
C:\Program Files\Microsoft SDKs\RIA Services\v1.0\Libraries\Server\System.Web.DomainServices.dll.
Yes normally it should work unless it doesn’t implement an empty default constructor.
Let me know how it goes.
[Unit]
April 2nd, 2009
at 6:25pm
Thank you so much! That attribute actually did the trick!
The type did implement an empty constructor. A pretty strange situation, however i didn’t need it too much on the client side anyway.
Hatim
April 2nd, 2009
at 8:08pm
Glad to help buddy
Bart Czernicki
April 4th, 2009
at 2:35am
Hatim,
Have you consumed data in RIA Services from other sources like a WCF REST Service or Velocity Distributed Caching?
Hatim
April 4th, 2009
at 10:25pm
Bart,
I am not sure what’s the scenario you are thinking of here. If you mean consuming a WCF REST or Velocity in your DomainService then that should normaly work. I haven’t tried this but if I you do please let me know how it works out for you.
Nigel Tunnicliffe
April 23rd, 2009
at 2:17pm
Which namesapce is the ‘Key’ attribute in - it does not seem to be in the DataAnnotations namespace which is where I expected it.
Thanks.
Ronald Widha
May 8th, 2009
at 7:51pm
Hatim, awesome explanation on metadata programming. Nicely written, concise and clear
However, making a comment about RIA itself; I’m not sure if I like the generation approach. Seems like it violates DRY badly. Is this a workaround to make RIA compatible with code generated entity classes such as EF?
Tatiana
July 3rd, 2009
at 12:38pm
Hatim, thank you very ma?h. I read the your articles and see daylight for with purpose (meaning or essence) was created:
- “link to ASP.NET server project” check box when creation project
- Domain Service class
- «Enable Client Access» check box when creation new domain service class.
It is of utmost importance for me. I am a business analyst. At the present time I study a development teqnologies of modern scalable n-tier web applicftions.
«Microsoft .NET RIA Services Overview» help me scantily in resolve this problem.
I will wait for yours Web publishing on this subject. It is an interesting question – What a main principles of identification new Domain Service classes in a design applications?
Nauman
December 10th, 2009
at 11:54am
Thanks, good explainatory article, i am using RIA services with LINQtoSQL.In my model class i have a db table class ‘Category’ when i tried to pass this class object to my custom exposed Domain Service classes method i get error in compiling silverlight application
error
DMPSLApp Parameter ‘category’ of domain operation entry ‘GetUserCategorySlide’ must be one of the predefined serializable types.
Here is my Custom Service Class code (written as you explained in this article)
[EnableClientAccess]
public class DMPCustomHelperDomainService : DomainService
{
private CategorySlideData objCategorySlidedata = new CategorySlideData();
public IQueryable GetUserCategorySlide(Category category
{
return this.objCategorySlidedata.GetUserCategorySlideCollection(category);
}
}
Can you please tell me what going wrong, why it does not accept this dbmodel class’Category’ object as in put parameter, but in actual domain service class i can send this ‘Category’ object ?
Ben
April 25th, 2010
at 1:18am
I can’t find/resolve references to these files system.web.ria.dll, system.web.domainservices.dll, and system.web.domainservices.EntityFramework.dll in a .Web project. I have re-installed RiaServices.msi Rel.4/8 (WCF RIA Services Release Candidate 2 for Silverlight 4 and Visual Studio 2010) after VS2010 final Rel.4/12 and silverlight4_tools.exe Rel.4/13
I do have the folder
C:\Program Files\Microsoft SDKs\RIA Services\v1.0\Libraries\Server\
but the above dlls are not there? Instead it has things like
System.ServiceModel.DomainServices.Server.dll,
System.ServiceModel.DomainServices.EntityFramework.dll, etc..
Do you have any hint?
Thank you!