Custom Domain Service Factory

Parameterized constructors are not allowed in WCF RIA domain service, however your object model may requires to have constructor with arguments. For example EmployeeService (in this example) is WCF RIA Domain Service which requires Authentication and have constructor with authenticated User instance as parameter. The activate the service in such scenario you need to provide your own custom domain factory calls and plug the factory in Application_Start event of Global.aspx.

Sample Employee Service class

[EnableClientAccess()]
[RequiresAuthentication()]
public class EmployeeService : DomainService
{
        
    public EmployeeService(User user)
    {
            
    }
    //......
        
}

Custom Domain service factory

internal sealed class DomainServiceFactory : IDomainServiceFactory
{
    private IDomainServiceFactory _defaultFactory;


    public DomainServiceFactory(IDomainServiceFactory defaultFactory)
    {
        _defaultFactory = defaultFactory;
    }

    public static void Setup()
    {
        if (!(DomainService.Factory is DomainServiceFactory))
        {
            DomainService.Factory = new DomainServiceFactory(DomainService.Factory);
        }
    }

    public DomainService CreateDomainService(Type domainServiceType, DomainServiceContext context)
    {
        if (domainServiceType == typeof(EmployeeService))
        {
            DomainServiceContext authServiceContext =
                new DomainServiceContext(context, DomainOperationType.Query);
            AuthenticationService authService =
                (AuthenticationService)_defaultFactory.CreateDomainService(typeof(AuthenticationService), authServiceContext);
            User user = authService.GetUser();

            DomainService domainService = (DomainService)Activator.CreateInstance(domainServiceType, user);
            domainService.Initialize(context);
            return domainService;
        }
        else
        {
            return _defaultFactory.CreateDomainService(domainServiceType, context);
        }
    }

    public void ReleaseDomainService(DomainService domainService)
    {
        if (domainService is EmployeeService)
        {
            domainService.Dispose();
        }
        else
        {
            _defaultFactory.ReleaseDomainService(domainService);
        }
    }
}

Setup Domain service factory

protected void Application_Start(object sender, EventArgs e)
{
    DomainServiceFactory.Setup();

}

Silverlight 4.0 with WCF Ria (2008)

Visual studio 2010 comes with nice Silverlight designer, But for LOB applications you always need data which can be pumped only via WCF RIA service (assuming you want to finish the project ASAP). Lot of programmers have still issues with WCF RIA 1.0 (for Vs2010) in terms of publishing the application on shared hosting domain. Actually this problem is because of not having correct version of .net framework on shared hosting domains. Example – my hosting provider has .net version 4.0.2xxx installed on web server. With 4.0.2 i got error saying Unrecognized attribute ‘multipleSiteBindingsEnabled’ where as on local machine i am having 4.0.3, and hosting provider will not install .net version on shared hosting domain until it get stable and they get some confidence ..So till than are we going to use Silverlight 3 ??

Well in my previous post i have tried to fix issues with WCF RIA on shared hosting domain which is working fine. Note that this fix is for WCF RIA Beta 1 which is compiled for Vs 2008. There is no fix available which could be applied to WCF RIA 1.0 (for 2010) in similar way. What we are going to do here is use best out of what we have i.e SL 4.0 + Vs2010 and WCF RIA + Vs2008.

Jumping directly to the point : How to work with Silverlight 4.0 (VS 2010) and WCF RIA (VS 2008)..

What you required:

  1. Vs 2008 with WCF RIA Beta
  2. Vs 2010 with Silverlight 4.0

How to build:

  1. Create WCF RIA Service Library using Visual Studio 2008. –
    1. Above step will create two libraries -one for silverlight project (client) and another for web server project.
  2. Create another Silverlight business app using Visual Studio 2008
    1. Add above assemblies to both projects (Sl+Web).- test it.
    2. Now delete the SL project created in step 2. we will only need wcf-web project from step 2.
  3. Now Open Vs 2010 and open the web project (existing) created in step 2 above.This will migrate you project from VS 2008 to 2010.
  4. Add new silverlight project to above solution.
  5. Go to properties of web project and link it with silverlight project in the group.

And that it !

For silverlight application you will use VS 2010 (framework 3.5) and for Ria service you need to launch Vs 2008 (project in step 1).

While you try to publish app on shared hosting domain,the fix which is provided in my previous articles can be applied. The WCF RIA libraries generated in step 1 could be migrated to WCF RIA 1.0 if in future issues with WCF got resolved and you need not to change you silverlight app.

I still need to test the solution mentioned above (althought i had quick test before writting this article) to ensure that we are not lossing any thing..

Let me know if template project is required for this, i will be glad to help..

So what you are thinking now go-ahead….

 You may need to refer fix2 to catch exceptions from server..

Implementing Silverlight Faults in SL 3.0 with RIA Domain Services – Fix 2

Last month i have post article on how to host Silverlight (With RIA Domain services) project on shared domain. In that article (Link is Here ) some web.config settings are recommended to configure RIA end points. While working tonight i have noticed that Silverlight clients are not able to catch exceptions raised by DomainService ! and that’s because we have replaced the RIA default end point configurations via web.config settings. Silverlight version 3 enables support for the Windows Communication Foundation (WCF) SOAP fault programming model, which allows the service to communicate error conditions to the client. We need to perform following steps to enable error catch to Silverlight client. To send faults to a Silverlight client that are accessible to it, an WCF service must modify the way it sends its fault messages. The key change needed is for WCF to return fault messages with a HTTP 200 response code instead of the HTTP 500 response code. This change enables Silverlight to read the body of the message and also enables WCF clients of the same service to continue working using their normal fault-handling procedures.
The modification on the server can be made by defining a WCF endpoint behavior for Silverlight faults. The following code sample shows how to do this.
Create Project Paris.Silverlight and add class SilverlightFaultBehavior
Copy the code as mentioned below (This has been taken from assembly System.Web.Ria 2.0.0.0) or follow link

1.

using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace Paris.Silverlight
{
    public class SilverlightFaultBehavior : BehaviorExtensionElement, IEndpointBehavior
    {
        public SilverlightFaultBehavior()
        {

        }
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            SilverlightFaultMessageInspector inspector = new SilverlightFaultMessageInspector();
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }

        public override System.Type BehaviorType
        {
            get { return typeof(SilverlightFaultBehavior); }
        }

        protected override object CreateBehavior()
        {
            return new SilverlightFaultBehavior();
        }
    }

    public class SilverlightFaultMessageInspector : IDispatchMessageInspector
    {
        object IDispatchMessageInspector.AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            // Do nothing to the incoming message
            return null;
        }

        void IDispatchMessageInspector.BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            if (reply.IsFault)
            {
                HttpResponseMessageProperty property = new HttpResponseMessageProperty();
                property.StatusCode = System.Net.HttpStatusCode.OK; // 200

                reply.Properties[HttpResponseMessageProperty.Name] = property;
            }
        }

    }
}


Compile the project and add reference of above assembly to your RIA web service project.

Now we need to register the custom behaviorExtension element in web.config before doing that i would recommend to find full Qualified name of your assembly holding above class.

Type code mentioned below to your start up aspx page

string name = typeof(Paris.Silverlight.SilverlightFaultBehavior).AssemblyQualifiedName;

copy the value of name , in my case thay are
Paris.Silverlight.SilverlightFaultBehavior, SilverlightFaultBehavior, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

Note that you need to copy the same to web .config. Service may not work if you may put extra space or new line character in web .config.

When registering a custom behaviorExtension element in your app.config to set up a custom endpoint behavior
for a WCF service, WCF seems to require that the type attribute of the behaviorExtension matches *exactly* the assembly qualified name of the BehaviorExtensionElement class at a string level.

2. Add RIASilverlightFaultBehavior to your web.config and also add behaviorConfiguration=”RIASilverlightFaultBehavior” to each endpoint of RIA service where address=”/binary”

<service name="SparkExams.Web.UserRegistrationService" behaviorConfiguration="RIAServiceBehavior">
  <endpoint address="" binding="wsHttpBinding" contract="SparkExams.Web.UserRegistrationService" />
  <endpoint address="/soap" binding="basicHttpBinding" contract="SparkExams.Web.UserRegistrationService"/>
  <endpoint address="/binary" binding="customBinding"  bindingConfiguration="BinaryHttpBinding"
  contract="SparkExams.Web.UserRegistrationService"  behaviorConfiguration="RIASilverlightFaultBehavior"/>
</service>

Don’t forget to deploy SilverlightFaultBehavior assembly to your web server.
Please also note that type attribute is space sensitive, use typeof(className).AssemblyQualifiedName to find type .
Code and article has been tested against Vs 2008, and RIA 2.0 Beta.

Click here for details on Fault Strategies in SL3

Adding User Management to your business application

RIA “Business application” template adds login screen and logic to use Membership and role management.You just need to alter your web.cofig and create aspnet database.The configuration has already been cover in previous articles. In this article i will create Userdata metatdata class (for MembershipUser) and display all users in the system in DataGrid.User can also update some flags like IsApproved and IsLocked from this screen.User can also select single or multiple users from grid and click on delete to delete users from Membership. Users is not allowed to use IsLocked flag to lock account however he can use this checkbox to unlock the locked accounts.In this article we will learn how we can bind data grid to our custom entity,Update the data on web service when ever user changes anything in client side.Add custom validation to ensure that user is allowed to set IsLocked=false; and is not allowed to set IsLocked = true;

1. To your web project add class UserData to Modles which will be used hold metadata for MembershipUser.

public class UserData
{  [Display(Order = 1, Name = "User Name")]
    [Key]
    public string UserName { get; set; }
    [ReadOnly(true)]
    [Display(Order = 2, Name = "Email")]
    public string Email { get; set; }
    [ReadOnly(true)]
    [Display(Order = 3, Name = "Created On")]
    public DateTime CreationDate { get; set; }
    [ReadOnly(true)]
    [Display(Order = 4, Name = "Last Login")]
    public DateTime LastLoginDate { get; set; }
    [ReadOnly(true)]
    [Display(Order = 5, Name = "Is Online")]
    public bool IsOnline { get; set; }
    [Display(Order = 6, Name = "Is Approved")]
    [Editable(true)]
    public bool IsApproved { get; set; }
    [Display(Order = 7, Name = "Is Locked")]
    [Editable(true)]
    [CustomValidation(typeof(Shared.Security.IsLockedOutValidator), "IsLockedOutValidValue")]
    public bool IsLockedOut { get; set; }
    [ReadOnly(true)]
    [Display(Order = 8, Name = "Last Lockout")]
    public DateTime LastLockoutDate
    {
        get;
        set;
    }
    [ReadOnly(true)]
    [Display(Order = 9, Name = "Last Activity")]
    public DateTime LastActivityDate { get; set; }
}

 Note that properties IsApproved and IsLocked are editable and rest of all properties are readonly.Also custom validator class IsLockedOutValidator is used to validate that user is allowed to set IsLocked = false; and is not allowed to set IsLocked =true;

2. Now again to you web project in shared folder add the class IsLockedOutValidator and name the file as IsLockedOutValidator.shared.cs.This will ensure that IsLockedOutValidator class is exposed to client for validations.Add following code to your class IsLockedOutValidator

public class IsLockedOutValidator
{
    public static ValidationResult IsLockedOutValidValue(bool value, ValidationContext context)
    {
        if (value == true)
        {
            return new ValidationResult("You can not lock user accounts ! Please use IsApproved to disable users.");
        }
        else
        {
            return ValidationResult.Success;
        }
    }
}

 3. Now you need to add following methods to you Domainservice class

    public IEnumerable<Models.Security.UserData> GetUsers()
    {
        List<Models.Security.UserData> Users = new List<Models.Security.UserData>();
        foreach (MembershipUser u in Membership.GetAllUsers())
        {
            Users.Add(new Models.Security.UserData
            {
                UserName = u.UserName,
                CreationDate = u.CreationDate,
                Email = u.Email,
                IsApproved = u.IsApproved,
                IsLockedOut = u.IsLockedOut,
                IsOnline = u.IsOnline,
                LastActivityDate = u.LastActivityDate,
                LastLockoutDate = u.LastLockoutDate,
                LastLoginDate = u.LastLoginDate,
            });
        }
        return Users;
    }
   public void DeleteUserData(UserData data)
    {
        Membership.DeleteUser(data.UserName);
    }
   public void UpdateUserData(UserData data)
    {
        //We need to only ensure that two properties are editable and values can be changed.
        MembershipUser u = Membership.GetUser(data.UserName);
        if (data.IsLockedOut == false && u.IsLockedOut)
            u.UnlockUser();
        u.IsApproved = data.IsApproved;
        Membership.UpdateUser(u);
    }

 Please note that if your entity name is UserData then method name used for addition,deletion,of update operation should be Update[EntityName],Delete[EntityName] i.e UpdateUserData,DeleteUserData .. etc.

OK to this stage we have create our class to expose users registered under membership. We have exposed methods to update and delete users from membership. We have also added our custom validation shared class.

Now let’s talk about silverlight client.

To your xaml file add namespace “xmlns:dataGrid=”clr-namespace:System.Windows.Controls;assembly = System.Windows.Controls.Data” (you need to add appropriate ref. Before doing this)

<dataGrid:DataGrid x:Name="grdUsers"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserSortColumns="True"
AutoGenerateColumns="True"
BorderBrush="Gray" />

To code behind file add

SecurityContext _context = new SecurityContext(); //Assuming this is your web service context

In constructor of code behind

    this.Loaded += new RoutedEventHandler(Configurations_Loaded);
    grdUsers.RowEditEnded += new EventHandler<DataGridRowEditEndedEventArgs>(grdUsers_RowEditEnded);
   void grdUsers_RowEditEnded(object sender, DataGridRowEditEndedEventArgs e)
    {
        _context.SubmitChanges();
    }
   void Configurations_Loaded(object sender, RoutedEventArgs e)
    {
        grdUsers.ItemsSource = _context.UserDatas;
        _context.Load(_context.GetUsersQuery());
    }

Add button “DeleteUser(s)” and on click event add following code

  private void DeleteUser(object sender, RoutedEventArgs e)
    {
        IEnumerable<UserData> list = grdUsers.SelectedItems.Cast<UserData>();
        List<UserData> alist = new List<UserData>();
        alist.AddRange(list);
        foreach (UserData u in alist)
        {
            _context.UserDatas.Remove(u);
        }
        _context.SubmitChanges();
    }

Note that we are removing or changing _context.UserDatas and just using _context.SubmitChnages();This is call the appropriate UpdateUserData or DeleteUserData on web service.

RIA WCF Configuration (Finally Resolved):

Finally after 3 days and nights i was able to find the solution for hosting RIA services on shared hosting environment and without changing anything on IIS. Yes it is possible…

Why : RIA framework dynamically creates WCF service (Domain services) and add endpoints to the service.It first check if endpoint does’nt exist then create it,and it checks for 3 endpoints (http,soap and binary).After creating end points it adds authentication schema to end points.It picks IIS authentication schema’s and tries to apply on end points and failed to apply.

If we could create desired end points in web.config RIA framework will not create or do anything with endpoints and it works succesfully ..

You just need to follow the simple steps mentioned below :

1. Add following code to you web.config to solve issue “This collection already contains an address with scheme http..”

<serviceHostingEnvironment aspNetCompatibilityEnabled="true">
  <baseAddressPrefixFilters>
    <add prefix="http://www.yoursite.com"/>
  </baseAddressPrefixFilters>
</serviceHostingEnvironment>

Note: Your service can be only accessed by url mentioned in above settings. As configured above you can’t access your service via http://yoursite.com.
You could also use factory code to host WCF (see below) to resolve this error however alone with that you need to create svc files for each domain service.

2.Add AspNetCompatibilityRequirementsMode attribute to your RIA Domain services classes
Eg .Attrubtes added to AuthenticationService class under services folder

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class AuthenticationService : AuthenticationBase<User> { }

RIA framework dynamically apply these attributes after creating end points.Since we are now bypassing endpoint creation , we need to manually apply these attributes.

3. For each RIA domain service add following to you configuration file.
Eg. Is shown for AuthenticationService and UserRegistrationService
Where SparkExams is my custom namespace.

<services>
  <service name="SparkExams.Web.AuthenticationService"
  behaviorConfiguration="RIAServiceBehavior">
    <endpoint address="" binding="wsHttpBinding"
    contract="SparkExams.Web.AuthenticationService" />
    <endpoint address="/soap"
    binding="basicHttpBinding"
    contract="SparkExams.Web.AuthenticationService" />
    <endpoint address="/binary"
    binding="customBinding"
    bindingConfiguration="BinaryHttpBinding"
    contract="SparkExams.Web.AuthenticationService" />
  </service>
  <service name="SparkExams.Web.UserRegistrationService"
  behaviorConfiguration="RIAServiceBehavior">
    <endpoint address=""
    binding="wsHttpBinding"
    contract="SparkExams.Web.UserRegistrationService" />
    <endpoint address="/soap"
    binding="basicHttpBinding"
    contract="SparkExams.Web.UserRegistrationService" />
    <endpoint address="/binary"
    binding="customBinding" bindingConfiguration="BinaryHttpBinding"
    contract="SparkExams.Web.UserRegistrationService" />
  </service>

Please note that RIA adds 3 endpoints and if any of these endpoints are missing from web.config it will throw “IIS specified authentication schemes ‘Basic, Anonymous’…” error.
Add following behaviours and bindings to your web.config

<behaviors>
  <serviceBehaviors>
    <behavior name="RIAServiceBehavior">
      <serviceMetadata httpGetEnabled="true" />
      <serviceDebug includeExceptionDetailInFaults="false" />
    </behavior>
  </serviceBehaviors>
</behaviors>
<bindings>
  <customBinding>
    <binding name="BinaryHttpBinding">
      <binaryMessageEncoding />
      <httpTransport />
    </binding>
  </customBinding>
</bindings>

Test you wcf end points using WCF client test tool (Test client for Windows Communication Foundation services.) WcfTestClient.exe : Go to VS 2008 Console and type WcfTestClient.exe.

Note that there is no need to host you service,or change IIS settings by ISP.
Update : While working on SL project i have noticed that SL is not able to recieve faults/exceptions thrown by RIA domain service.Please follow article to fix the issue “Silverlight Faults in SL 3.0 with RIA Domain Services – Fix 2 – Must read

Finally code and live demo application link are here..

Demo Application link
/rajnish/RiaTest/

 Custom service link
/rajnish/RiaTest/DeployTest-Web-services-CustomService.svc

Link to binay files for above links
/rajnish/uploads/code/RiaTest/Binary.zip

//Source code of application (Web.config has been changed in binary.zip)
/rajnish/uploads/code/RiaTest/Source.zip

If you are having any problem related to hosting Sl,copy binay.zip contents to your virtual directory on web server and modify web.config (bottom)
add prefix=”/rajnish/

If you have any problem with your code , then please download the binary.zip extract the contents and create virtual directory say RiaTest on your domain and copy the contents of Binary.zip (files like Default.aspx,DeployTestTestPage.aspx etc) to your web server.Change web.config “prefix section as mentioned above.Eg. on my webserver www.rajneeshnoonia.com i have created RiaTest virtual directory and copyied the contents of binary.zip into it.first step is to test your web service with URL like /rajnish/RiaTest/DeployTest-Web-services-CustomService.svc. if web service is ok you can launch your silverlight application.

Note: the site will not work if you try to launch with /rajnish/… rather it will work if you try /rajnish/

Configure .Net RIA Service – hosted on shared domain

1.Mark “copy to local” these 3 assembly in your web project:
1) System.Web.DomainService
2) System.Web.Ria
3) System.ComponentModel.DataAnnotations

This will deploy above mentioned assemblies in you bin folder

Test your web service via like
http://www.yoursite.com/virDir/namespace-Web-AuthenticationService.svc
where namespace.Web.AuthenticationService is your RIA domain service.
Internally at runtime RIA replaces . with –
Here you will get error as “This collection already contains an address with scheme http. There can be at most one address per scheme in this collection.”

2.Add following code to your web.config
<serviceHostingEnvironment aspNetCompatibilityEnabled=”true”>
<baseAddressPrefixFilters>
<add prefix=”http://www.yoursite.com”/&gt;
</baseAddressPrefixFilters>
</serviceHostingEnvironment>

You also need to change the service reference inside your Silverlight code to point to the service with an absolute address. Following is a sample that I used in my application:
3. open file Namespace.Web.g.cs in hidden folder Generated_Code under silverlight project.
Replace UriKind something like new Uri(“namespace-Web-UserRegistrationService.svc”, UriKind.Relative) to new Uri(“namespace-Web-UserRegistrationService.svc”, UriKind.Absolute)

Try to re run your service url you will now get error “IIS specified authentication schemes ‘Basic, Anonymous’, but the binding only supports specification of exactly one authentication scheme. Valid authentication schemes are Digest, Negotiate, NTLM, Basic, or Anonymous. Change the IIS settings so that only a single authentication scheme is used.”

4.Reduced your site’s authentication to “Anonymous” and everything worked great. The exact details on how you do this vary from ISP to ISP

Microsoft is still working on this and will soon (hopefully) fix the issue’s in further release of ria services.

Configuring RIA to use roleManager, membership and profile.

Create Silverlight Project “Business Navingation application” in Visual Studio 2010 Beta 2 and configure SQL express database to generate schema for rolemanager, membership and profile feature using command

Launch Visual Studio Command prompt (Run as administrator)
Type command
Aspnet_regsql.exe –E–S sqlinstance–A rmp
This will create sql express database aspnetdb

Visit links to see how to configure components mentioned above

Configure Role Manager
Configure Membership Manager
Configure profile Manager

Edit web.config file as
<configuration>
<!– Connection to database (also used by rolemanager,membership manager and profile manager) –>
<connectionStrings>
<add name=”SQLDataSource” connectionString=”Data Source= .SQLExpress;Initial Catalog=aspnetdb;Integrated Security=SSPI;” />
</connectionStrings>
……
<!– Configure Role Manager (See : http://msdn.microsoft.com/en-us/library/ms998314.aspx)–>
<roleManager enabled=”true” defaultProvider=”SqlDataRoleProvider”>
<providers> <clear />
<add name=”SqlDataRoleProvider”
type=”System.Web.Security.SqlRoleProvider”
connectionStringName=”SQLDataSource”
applicationName=” APPName ” />
</providers>
</roleManager>
<!– Configure Membership Manager (See : http://msdn.microsoft.com/en-us/library/ms998347.aspx)–>
<membership defaultProvider=”SqlDataMemberProvider” userIsOnlineTimeWindow=”1″>
<providers> <clear />
<add name=”SqlDataMemberProvider”
type=”System.Web.Security.SqlMembershipProvider”
connectionStringName=”SQLDataSource”
applicationName=” APPName “
enablePasswordRetrieval=”false”
enablePasswordReset=”true”
requiresQuestionAndAnswer=”true”
requiresUniqueEmail=”true”
passwordFormat=”Hashed” />
</providers>
</membership>

<!– Configure profile Manager (See : http://msdn.microsoft.com/en-us/library/014bec1k.aspx)–>
<profile defaultProvider=”SqlDataProfileProvider”>
<properties>
<add name=”FriendlyName”/>
</properties>
<providers> <clear />
<add name=”SqlDataProfileProvider” type=”System.Web.Profile.SqlProfileProvider”
connectionStringName=”SQLDataSource” applicationName=”APPName” />
</providers>
</profile>
……
</system.web>
…..

For more details please follow links mentioned in post