Minggu, 23 Januari 2011

Business Apps Example for Silverlight 3 RTM and .NET RIA Services July Update: Part 18: Custom Linq Provider


Continuing in our discussion of Silverlight 3 and the update to .NET RIA Services. I have been updating the example from my Mix09 talk “building business applications with Silverlight 3”. RIA Services is very much an extension of the LINQ project. Effectively you can think of RIA Services as n-tier LINQ. As such, I thought it would be interesting to show how any of the tons of Linq Providers can be used with RIA Services.


You can watch the original video of the full session


The demo requires (all 100% free and always free):



  1. VS2008 SP1 (Which includes Sql Express 2008)

  2. Silverlight 3 RTM

  3. .NET RIA Services July '09 Preview

Also, download the full demo files and check out the running application.


Here is the general application pattern we are looking at this time:


image


Way back in Part 8: WCF I showed off how to get your data from a WCF services (rather than Entity Framework) via RIA Services. One part I was never quite happy with is that I needed to pass the page number into the Query method. While David Poll and I developed a neat little paging pattern that worked with this, it certainly felt like a hack.. and Jason Allor, our development manager totally agreed. The core problem is I didn’t have a IQueryable that knew about my WCF services to return. Jason assured me this would be easy to do based on the LINQ to TerraServer Provider Sampleand Matt Warren’s excellent set of posts. So I dared him to try it and he did!


What Jason ended up with is pretty cool. It does not cover 100% of the cases that RIA Services might use, but it does cover a lot or them and all the source is here, so feel free to use and extend.


In my original WCF example, my query method looked like:



public IQueryable<SuperEmployee> GetSuperEmployees(int pageNumber)
{
    return this.Context.GetSuperEmployees(pageNumber)
               .Where(emp => emp.Issues > 100)
               .OrderBy(emp => emp.EmployeeID)
               .Select(emp =>
                   new MyApp.Web.SuperEmployee()
                   {
                       EmployeeID = emp.EmployeeID,
                       Gender = emp.Gender,
                       Issues = emp.Issues,
                       LastEdit = emp.LastEdit,
                       Name = emp.Name,
                       Origin = emp.Origin,
                       Publishers = emp.Publishers,
                       Sites = emp.Sites,
                   }).AsQueryable();
}

Notice that page number should really be handled by skip() and take() in the query rather than an explicit parameter that doesn’t compose well.


And with Jason’s cool new IQueryable to WCF implementation it is much clearner:



public IQueryable<SuperEmployee> GetSuperEmployees()
{
    return new LinqToSuperEmployeeService(this.context)
               .Where(emp => emp.Issues > 100);
}

Basically the LinqToSuperEmployeeService parses the Linq query and gives us access to the individual parts of the query.




internal class LinqToSuperEmployeeService : QueryableService<SuperEmployee>
{
    private SuperEmployeeServiceClient context;
 
    public LinqToSuperEmployeeService(SuperEmployeeServiceClient context)
    {
        this.context = context;
    }
    protected override object ExecuteQuery(QueryDetails details)
    {
        if (details.Count)
        {
            return this.context.GetSuperEmployeesCount(
                details.SkipSize, 
                details.PageSize, 
                details.OrderBy,
                details.Filters.ToArray());
        }
        else
        {
            return this.context.GetSuperEmployees(
                details.SkipSize, 
                details.PageSize, 
                details.OrderBy,
                details.Filters.ToArray())
           .Select(emp => ConvertUtils.Convert(emp));
        }
    }
}

For example, consider a query such as:



q.Skip(20).Take(10)
 .OrderBy(e => e.Name)
 .Where(e => e.Origin == 'Earth');


We’d end up calling the GetSuperEmployees(20,10,null,{“Earth”});


The base QueryableService will parse out the the SkipSize, PageSize and orderby information an puts it in the QueryDetails class. And then in the this overload of the Execute method we pluck those out and pass them on to the WCF service. You could just as easily pluck those values out and call a REST based service or generate some TSQL, etc.


If you wanted to reuse this functionality for your WCF service, you just derive your own subclass of QueryableService and do the right thing with the values.


One more cool thing, if you create your own subclass of of DomainService that can handle calling a particular WCF service things get even easier.



[EnableClientAccess()]
 public class SuperEmployeeDomainService : LinqToSuperEmployeeDomainService
 {
     public IQueryable<SuperEmployee> GetSuperEmployees()
     {
         return this.Context;
     }

And the code for your custom DomainService? Pretty easy as well.



public class LinqToSuperEmployeeDomainService : DomainService
{
    private SuperEmployeeServiceClient webServiceContext = new SuperEmployeeServiceClient();
 
    private LinqToSuperEmployeeService linqContext;
 
    protected LinqToSuperEmployeeDomainService()
    {
        this.linqContext = new LinqToSuperEmployeeService(this.webServiceContext);
    }
 
    public IQueryable<SuperEmployee> Context
    {
        get { return this.linqContext; }
    }
 
    public SuperEmployeeServiceClient WebServiceContext
    {
        get { return this.webServiceContext; }
    }
}

This part talked about how to use a custom Linq provider to make it very easy to call a WCF service to get data into your Silverlight client. The very simple Linq provider I show here can be easily customized to work with any service or other data source.

Business Apps Example for Silverlight 3 RTM and .NET RIA Services July Update: Part 19: ASP.NET Dynamic Data


Still have good fun updating my simple Mix 09 Business Application demo. In this part, I wanted to consider how we might build the admin site of our site.. Because it is just for a very small number of trusted users, I’d like to get something up and running quickly. I’d also like to share all by business logic and validation code between the Silverlight client and the WebAdmin site.

You can see the full series here.

The demo requires (all 100% free and always free):

  1. VS2008 SP1
  2. Silverlight 3 RTM
  3. .NET RIA Services July '09 Preview
  4. ASP.NET Dynamic Data Preview 4 Refresh

Also, download the full demo files

For this part we are going to focus on building out a WebAdmin page using ASP.NET Dynamic Data. Dynamic Data makes it very easy… dare I say trivial to build out a data-driven web application. The great thing is that it is super easy to extend and custom the application incrementally. Today’s Dynamic Data builds on top of Entity Framework and LinqToSql directly… With .NET RIA Services support, you can now work with any data source (as I have shown POCO, DataSet, WCF, Astoria, DTOs) as well as model your business\validation logic in a common way.

image

Just as a recap, in previous parts, we defined a DomainServices

[EnableClientAccess]



public class SuperEmployeeDomainService :



    LinqToEntitiesDomainService<NORTHWNDEntities>



{



  



    public IQueryable<SuperEmployee> GetSuperEmployees()



    {



        return this.Context.SuperEmployeeSet



                   .Where(emp=>emp.Issues>100)



                   .OrderBy(emp=>emp.EmployeeID);



    }



 



    public SuperEmployee GetSuperEmployee(int employeeID)



    {



        return this.Context.SuperEmployeeSet



                   .Where(emp => emp.EmployeeID == employeeID)



                   .FirstOrDefault();



    }



 



    public void InsertSuperEmployee(SuperEmployee superEmployee)



    {



        this.Context.AddToSuperEmployeeSet(superEmployee);



    }



 



    public void UpdateSuperEmployee(SuperEmployee currentSuperEmployee)



    {



        this.Context.AttachAsModified(currentSuperEmployee, this.ChangeSet.GetOriginal(currentSuperEmployee));



    }



 



    public void DeleteSuperEmployee(SuperEmployee superEmployee)



    {



        if ((superEmployee.EntityState == EntityState.Detached))



        {



            this.Context.Attach(superEmployee);



        }



        this.Context.DeleteObject(superEmployee);



    }




And built out a Silverlight application to work with it.



image





In this part, let’s start with, grab the DomainServiceProject from the ProjectTemplates direction in the ASP.NET Dynamic Data Preview 4 Refresh download. You can add this solution to your project or, as I did, merge it into an existing solution. If you do the merge option, be sure the get the code in web.config, global.asax, and the DynamicData directory.



Either way, the only code you need to edit to get the basic site up and running is in global.asax.





   1: public static void RegisterRoutes(RouteCollection routes) {



   2:     



   3:     DefaultModel.RegisterContext(



   4:         new DomainModelProvider(typeof(SuperEmployeeDomainService)), 



   5:         new ContextConfiguration() { ScaffoldAllTables = true });



   6:  



   7:     routes.Add(new DynamicDataRoute("{table}/{action}.aspx") {



   8:         Constraints = new RouteValueDictionary(new { action = "List|Details|Edit|Insert" }),



   9:         Model = DefaultModel



  10:     });




In line 4, we provide the SuperEmployeeDomainService… (see code from above). Everything else is left in the defaults from the template.



Hitting F5 and browsing to the WebAdmin page, you will see..



image



This default page, this is very easy to customize just by editing the WebAdmin.aspx



image



Clicking on a table brings us into a default view where we can do sorting, paging, detele and insert new, etc..



image



Clicking on edit (or new) will take us to a form that fully understands the data validation that we defined on our business model.



The next step is to use the full power of Dynamic Data to custom this app to be just what we need… Check out the videos and walk throughs to learn more about this.



Now, we have ended up with a Silverlight client and an ASP.NET client that both operate against the same data and application logic\validation. This is greatly reduce maintenance costs as we add more to the DomainService those just get picked up here.

Business Apps Example for Silverlight 3 RTM and .NET RIA Services July Update: Part 20: NHibernate


I am having a blast with the series where I am updating my simple Mix 09 Business Application demo. In this part, I wanted to explore one of the most popular data access solution for .NET – NHibernate. Many customers finds that the flexibility of NHibernate makes it easier for them to build maintainable and testable applications. As an aside, I think NHibernate is an excellent example of the vibrant open source community on .NET that I’d like to support.

You can see the full series here.

The demo requires (all 100% free and always free):

  1. VS2008 SP1
  2. Silverlight 3 RTM
  3. .NET RIA Services July '09 Preview
  4. NHibernate (w/ NHibernate Linq ) and Fluent NHibernate

Also, download the full demo files and check out the running application.

Basically what I wanted to do was switch over my DomainService from getting its data from Entity Framework to getting its data from NHibernate. The rest of the application stays effectively the same.

image

To start with I grabbed the code from the previous walk through, deleted the edmx file.

Below is the code I use to create the NHibernate SessionFactory. The fluent interface makes it very easy to configure.

   1: static ISessionFactory SessionFactory = CreateSessionFactory();



   2: static ISessionFactory CreateSessionFactory()



   3: {



   4:     return Fluently.Configure()



   5:       .Database(



   6:         MsSqlConfiguration.MsSql2005



   7:         .ConnectionString(c => c



   8:             .Is(ConfigurationManager.ConnectionStrings["MainConnStr"].ConnectionString)



   9:             )



  10:       )



  11:       .Mappings(m =>



  12:        m.FluentMappings.AddFromAssemblyOf<SuperEmployeeDomainService>())



  13:        .BuildSessionFactory();



  14:  



  15: }




Next, let’s look at the mapping class.. this shows how the data from the database are mapped into a .NET Class.





   1: public class SuperEmployeesMap : ClassMap<SuperEmployee>



   2: {



   3:     public SuperEmployeesMap()



   4:     {



   5:         WithTable("SuperEmployees");



   6:         Id(x => x.EmployeeID);



   7:         Map(x => x.Name);



   8:         Map(x => x.Gender);



   9:         Map(x => x.Issues);



  10:         Map(x => x.LastEdit);



  11:         Map(x => x.Origin);



  12:         Map(x => x.Publishers);



  13:         Map(x => x.Sites);



  14:     }



  15: }




And here is the type I map the data into. Notice this has the validation attributes for RIA Services to do the validation on the client and server.





   1: public class SuperEmployee



   2: {



   3:         [Key]



   4:         [ReadOnly(true)]



   5:         public virtual int EmployeeID {get; set;}



   6:  



   7:         [RegularExpression("^(?:m|M|male|Male|f|F|female|Female)${body}quot;, 



   8:             ErrorMessage = "Gender must be 'Male' or 'Female'")]



   9:         public virtual string Gender { get; set; }



  10:  



  11:         [Range(0, 10000,



  12:             ErrorMessage = "Issues must be between 0 and 1000")]



  13:         public virtual Nullable<int> Issues { get; set; }



  14:  



  15:         public virtual Nullable<DateTime> LastEdit { get; set; }



  16:  



  17:         [Required]



  18:         [StringLength(100)]



  19:         public virtual string Name { get; set; }



  20:  



  21:         public virtual string Origin { get; set; }



  22:  



  23:         public virtual string Publishers { get; set; }



  24:  



  25:         public virtual string Sites { get; set; }



  26:     }






That’s the bulk of the NHibernate specific code. Now let’s look at the DomainService..





   1: [EnableClientAccess()]



   2: public class SuperEmployeeDomainService : NHibernateDomainService



   3: {



   4:     public SuperEmployeeDomainService() :



   5:         base(SessionFactory) { }




First, in line 2, notice I have factored some of the plumbing code into a base class. This is very simple (not production ready) example.. we will look it in more detail later. Then we return the static SessionFactory the the base class. The SessionFactory is static such that it is created only once per AppDomain rather than on each request.





   1: public IQueryable<SuperEmployee> GetSuperEmployees()



   2: {



   3:     return Session.Linq<SuperEmployee>()



   4:             .Where(e => e.Issues > 10);



   5: }




Here we use NHibernate Linq to return an IQueryable that RIA Services can use to do paging, filtering, etc.



Then we have Insert and Update which are very basic..





   1: public void InsertSuperEmployee(SuperEmployee superEmployee)



   2: {



   3:     Session.Save(superEmployee);



   4: }



   5:  



   6: public void UpdateSuperEmployee(SuperEmployee currentSuperEmployee)



   7: {



   8:    Session.Update(currentSuperEmployee);



   9: }




Now, let’s take a look at the NHibernateDomainService base class… Again this is very basic, just to show the concepts..





   1: public class NHibernateDomainService : DomainService



   2: {



   3:  



   4:     protected ISession Session;



   5:    



   6:     public NHibernateDomainService(ISessionFactory sessionFactory)



   7:     {



   8:         this.Session = sessionFactory.OpenSession();



   9:     }



  10:  



  11:  



  12:     protected override void Dispose(bool disposing)



  13:     {



  14:         Session.Dispose();



  15:         base.Dispose(disposing);



  16:  



  17:     }



  18:     protected override void ExecuteChangeSet(ChangeSet changeSet)



  19:     {



  20:         using (var trans = Session.BeginTransaction())



  21:         {



  22:             base.ExecuteChangeSet(changeSet);



  23:             trans.Commit();



  24:         }



  25:     }




Notice in the constructor we create the Session… then we close the session in the Dispose() method.



The interesting bit is handling the ChangeSet… Notice we use a transaction to wrap the calls to our update\create methods. This ensures that if their is any failure we roll back the changes (this is the contract for DomainService)…



Now, because we plug into the DomainService pattern, we get our silverlight client with paging, filtering, etc



image



…and validated editing.



image





But we also get ASP.NET support via the DomainDataSource as in this example from the sitemap.aspx





   1: <asp:DomainDataSource runat="server" ID="SitemapDataSource" 



   2:     DomainServiceTypeName="MyApp.Web.SuperEmployeeDomainService" 



   3:     SelectMethod="GetSuperEmployees" />






And we get a REST based interface via the Astoria support for DomainService.



image





And we get ASP.NET Dynamic Data Support, again with paging, filtering,



image



and validating editing.



image



And all the other presentation tiers..





Wow, pretty cool..



For more on NHibernate, check out Ayende’s blog



For more NHibernate and DomainService, check out Chris van de Steeg’s blog post ASP.NET MVC, DynamicData, Domain-/RiaServices, Unity and NHibernate: Part 1