Geeks With Blogs

@bmdiaz
  • bmdiaz I like getting new toys to play with... it's like Christmas all over again! #geekshavemorefun about 812 days ago
  • bmdiaz @Buster_ESPN 3.,,,?))(665555568bujujjjjhjjjjjjiiooiiiiiiiuiuii nnbb about 913 days ago
  • bmdiaz @gblock I think Visual Studio also stores alot of crap in there too... about 986 days ago
  • bmdiaz I'm really diggin #android development! My java rust is finally starting to fall off... about 1144 days ago

bobby's blog randomly specific...

Recently, I started working on a new ASP.NET MVC 2 project and I wanted to reuse the data access (LINQ to SQL) and business logic methods (WCF RIA Services) that had been developed for a previous project that used Silverlight for the front-end.  I figured that I would be able to instantiate the various DomainService classes from within my controller’s action methods, because after all, the code for those services didn’t look very complicated.  WRONG!  I didn’t realize at first that some of the functionality is handled automatically by the framework when the domain services are hosted as WCF services.  After some initial searching, I came across an invaluable post by Joe McBride, which described how to get RIA Service .svc files to work in an MVC 2 Web Application, and another by Brad Abrams.  Unfortunately, Brad’s solution was for an earlier preview release of RIA Services and no longer works with the version that I am running (PDC Preview).

I have not tried the RC version of WCF RIA Services, so I am not sure if any of the issues I am having have been resolved, but I wanted to come up with a way to reuse the shared libraries so I wouldn’t have to write a non-RIA version that basically did the same thing.  The classes I came up with work with the scenarios I have encountered so far, but I wanted to go ahead and post the code in case someone else is having the same trouble I had.  Hopefully this will save you a few headaches!

1. Querying


When I first tried to use a DomainService class to perform a query inside one of my controller’s action methods, I got an error stating that “This DomainService has not been initialized.”  To solve this issue, I created an extension method for all DomainServices that creates the required DomainServiceContext and passes it to the service’s Initialize() method.  Here is the code for the extension method; notice that I am creating a sort of mock HttpContext for those cases when the service is running outside of IIS, such as during unit testing!

    public static class ServiceExtensions

    {

        /// <summary>

        /// Initializes the domain service by creating a new <see cref="DomainServiceContext"/>

        /// and calling the base DomainService.Initialize(DomainServiceContext) method.

        /// </summary>

        /// <typeparam name="TService">The type of the service.</typeparam>

        /// <param name="service">The service.</param>

        /// <returns></returns>

        public static TService Initialize<TService>(this TService service)

            where TService : DomainService

        {

            var context = CreateDomainServiceContext();

            service.Initialize(context);

            return service;

        }

 

        private static DomainServiceContext CreateDomainServiceContext()

        {

            var provider = new ServiceProvider(new HttpContextWrapper(GetHttpContext()));

            return new DomainServiceContext(provider, DomainOperationType.Query);

        }

 

        private static HttpContext GetHttpContext()

        {

            var context = HttpContext.Current;

 

#if DEBUG

            // create a mock HttpContext to use during unit testing...

            if ( context == null )

            {

                var writer = new StringWriter();

                var request = new SimpleWorkerRequest("/", "/",

                    String.Empty, String.Empty, writer);

 

                context = new HttpContext(request)

                {

                    User = new GenericPrincipal(new GenericIdentity("debug"), null)

                };

            }

#endif

 

            return context;

        }

    }

 

With that in place, I can use it almost as normally as my first attempt, except with a call to Initialize():

    public ActionResult Index()

    {

        var service = new NorthwindService().Initialize();

        var customers = service.GetCustomers();

 

        return View(customers);

    }


2. Insert / Update / Delete


Once I got the records showing up, I was trying to insert new records or update existing data when I ran into the next issue.  I say issue because I wasn’t getting any kind of error, which made it a little difficult to track down.  But once I realized that that the DataContext.SubmitChanges() method gets called automatically at the end of each domain service submit operation, I could start working on a way to mimic the behavior of a hosted domain service.  What I came up with, was a base class called LinqToSqlRepository<T> that basically sits between your implementation and the default LinqToSqlDomainService<T> class.

    [EnableClientAccess()]

    public class NorthwindService : LinqToSqlRepository<NorthwindDataContext>

    {

        public IQueryable<Customer> GetCustomers()

        {

            return this.DataContext.Customers;

        }

 

        public void InsertCustomer(Customer customer)

        {

            this.DataContext.Customers.InsertOnSubmit(customer);

        }

 

        public void UpdateCustomer(Customer currentCustomer)

        {

            this.DataContext.Customers.TryAttach(currentCustomer,

                this.ChangeSet.GetOriginal(currentCustomer));

        }

 

        public void DeleteCustomer(Customer customer)

        {

            this.DataContext.Customers.TryAttach(customer);

            this.DataContext.Customers.DeleteOnSubmit(customer);

        }

    }


Notice the new base class name (just change LinqToSqlDomainService to LinqToSqlRepository).  I also added a couple of DataContext (for Table<T>) extension methods called TryAttach that will check to see if the supplied entity is already attached before attempting to attach it, which would cause an error!

3. LinqToSqlRepository<T>


Below is the code for the LinqToSqlRepository class.  The comments are pretty self explanatory, but be aware of the [IgnoreOperation] attributes on the generic repository methods, which ensures that they will be ignored by the code generator and not available in the Silverlight client application.

    /// <summary>

    /// Provides generic repository methods on top of the standard

    /// <see cref="LinqToSqlDomainService&lt;TContext&gt;"/> functionality.

    /// </summary>

    /// <typeparam name="TContext">The type of the context.</typeparam>

    public abstract class LinqToSqlRepository<TContext> : LinqToSqlDomainService<TContext>

        where TContext : System.Data.Linq.DataContext, new()

    {

        /// <summary>

        /// Retrieves an instance of an entity using it's unique identifier.

        /// </summary>

        /// <typeparam name="TEntity">The type of the entity.</typeparam>

        /// <param name="keyValues">The key values.</param>

        /// <returns></returns>

        [IgnoreOperation]

        public virtual TEntity GetById<TEntity>(params object[] keyValues) where TEntity : class

        {

            var table = this.DataContext.GetTable<TEntity>();

            var mapping = this.DataContext.Mapping.GetTable(typeof(TEntity));

 

            var keys = mapping.RowType.IdentityMembers

                .Select((m, i) => m.Name + " = @" + i)

                .ToArray();

 

            return table.Where(String.Join(" && ", keys), keyValues).FirstOrDefault();

        }

 

        /// <summary>

        /// Creates a new query that can be executed to retrieve a collection

        /// of entities from the <see cref="DataContext"/>.

        /// </summary>

        /// <typeparam name="TEntity">The type of the entity.</typeparam>

        /// <returns></returns>

        [IgnoreOperation]

        public virtual IQueryable<TEntity> GetEntityQuery<TEntity>() where TEntity : class

        {

            return this.DataContext.GetTable<TEntity>();

        }

 

        /// <summary>

        /// Inserts the specified entity.

        /// </summary>

        /// <typeparam name="TEntity">The type of the entity.</typeparam>

        /// <param name="entity">The entity.</param>

        /// <returns></returns>

        [IgnoreOperation]

        public virtual bool Insert<TEntity>(TEntity entity) where TEntity : class

        {

            //var table = this.DataContext.GetTable<TEntity>();

            //table.InsertOnSubmit(entity);

 

            return this.Submit(entity, null, DomainOperation.Insert);

        }

 

        /// <summary>

        /// Updates the specified entity.

        /// </summary>

        /// <typeparam name="TEntity">The type of the entity.</typeparam>

        /// <param name="entity">The entity.</param>

        /// <returns></returns>

        [IgnoreOperation]

        public virtual bool Update<TEntity>(TEntity entity) where TEntity : class

        {

            return this.Update(entity, null);

        }

 

        /// <summary>

        /// Updates the specified entity.

        /// </summary>

        /// <typeparam name="TEntity">The type of the entity.</typeparam>

        /// <param name="entity">The entity.</param>

        /// <param name="original">The original.</param>

        /// <returns></returns>

        [IgnoreOperation]

        public virtual bool Update<TEntity>(TEntity entity, TEntity original)

            where TEntity : class

        {

            if ( original == null )

            {

                original = GetOriginal(entity);

            }

 

            var table = this.DataContext.GetTable<TEntity>();

            table.TryAttach(entity, original);

 

            return this.Submit(entity, original, DomainOperation.Update);

        }

 

        /// <summary>

        /// Deletes the specified entity.

        /// </summary>

        /// <typeparam name="TEntity">The type of the entity.</typeparam>

        /// <param name="entity">The entity.</param>

        /// <returns></returns>

        [IgnoreOperation]

        public virtual bool Delete<TEntity>(TEntity entity) where TEntity : class

        {

            //var table = this.DataContext.GetTable<TEntity>();

            //table.TryAttach(entity);

            //table.DeleteOnSubmit(entity);

 

            return this.Submit(entity, null, DomainOperation.Delete);

        }

 

        protected virtual bool Submit(Object entity, Object original, DomainOperation operation)

        {

            var entry = new ChangeSetEntry(0, entity, original, operation);

            var changes = new ChangeSet(new ChangeSetEntry[] { entry });

            return base.Submit(changes);

        }

 

        private TEntity GetOriginal<TEntity>(TEntity entity) where TEntity : class

        {

            var context = CreateDataContext();

            var table = context.GetTable<TEntity>();

            return table.FirstOrDefault(e => e == entity);

        }

    }


4. Conclusion


So there you have it, a fully functional Repository implementation for your RIA Domain Services that can be consumed by your ASP.NET and MVC applications.  I have uploaded the source code along with unit tests and a sample web application that queries the Customers table from inside a Controller, as well as a Silverlight usage example.

As always, I welcome any comments or suggestions on the approach I have taken.  If there is enough interest, I plan on contacting Colin Blair or maybe even the man himself, Brad Abrams, to see if this is something worthy of inclusion in the WCF RIA Services Contrib project.  What do you think?

Enjoy!

Posted on Saturday, April 3, 2010 4:31 AM Silverlight , ASP.NET , RIA , WCF , MVC | Back to top


Comments on this post: Using RIA DomainServices with ASP.NET and MVC 2

# re: Using RIA DomainServices with ASP.NET and MVC 2
Requesting Gravatar...
I don't know enough about ASP.NET and MVC2 to decide if it should be in Contrib or not. Your repository code is interesting but I would be happier if it was using the *OnSubmit instead of doing a submit themselves. Fredrik Normen is the one pushing the Repository stuff the most with RIA Services, you may want to check his blogs and see what he thinks.
Left by Colin Blair on Apr 20, 2010 12:07 PM

# re: Using RIA DomainServices with ASP.NET and MVC 2
Requesting Gravatar...
Brilliant, just what I was looking for. Many thanks!
Left by Benjamin Howarth on Jul 05, 2010 5:12 AM

# re: Using RIA DomainServices with ASP.NET and MVC 2
Requesting Gravatar...
Good to read about your experience... thank you!
Left by Asad Iqbal on Jul 05, 2010 10:07 PM

# re: Using RIA DomainServices with ASP.NET and MVC 2
Requesting Gravatar...
There are a bunch of other posts that refer to this one for a HowTo on wiring up MVC with WCF RIA services. We have an a silverlight 5 app that uses the built-in WCF RIA project properties to wiring it to our back end WCF RIA / Domain Service / EF4.1 data layer. The silverlight client and back end data service are separated by WCF RIA. This example shows the MVC app and domain service part of the same project. Does someone have an example of how to accomplish the same thing with MVC 3 and a WCF RIA data layer? What I'm looking for is how to put WCF RIA in between the controller and domain service. I don't see anything in the above example creating a WCF connection to a WCF service Uri. Any help would be appreciated.
Left by Adam Gilman on Feb 09, 2012 2:12 PM

# re: Using RIA DomainServices with ASP.NET and MVC 2
Requesting Gravatar...
The source code link is dead. Does anybody have a link to were the source code is?
Left by Phillip Donegan on Mar 22, 2013 10:34 AM

Your comment:
 (will show your gravatar)
 


Copyright © Bobby Diaz | Powered by: GeeksWithBlogs.net | Join free