Search code examples
asp.net-mvc-3modelasp.net-4.0data-access-layerwcf-rest

How to setup a DAL RESTful service with EFCodeFirst for MVC applications?


For an MVC3 application I want to create a reusable DAL that is accessed as a service, as this will be used for other projects in the future.

I created all the entities with TT templates and EFCodeFirst in a separate project, then consumed it in a RESTful WCF service.

The structure of this service seems a bit different from other WCF services I have written where I have specified RESTful signatures and optional JSON responses as method decorators in the service's interface, ie:

    [WebGet(UriTemplate = "GetCollection")]
    public List<SampleItem> GetCollection()
    {
        // TODO: Replace the current implementation to return a collection of SampleItem instances
        return new List<SampleItem>() { new SampleItem() { Id = 1, StringValue = "Hello" } };
    }

    [WebInvoke(UriTemplate = "", Method = "POST")]
    public SampleItem Create(SampleItem instance)
    {
        // TODO: Add the new instance of SampleItem to the collection
        throw new NotImplementedException();
    }

Where this RESTful WCF service (created from the RESTful WCF option) differs is that there is no interface, and I can decorate the service methods with what I need - that's fine. The service will expose methods like GetUserByID(int id), etc.

The issue is that I want to use this in a MVC3 application, and am not clear on how to hook up the models to my service and would like some direction in accomplishing this.

Thanks.


Solution

  • Assume you want to expose an entity called Person. The WCF REST service may look as follows:

    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]    
    public partial class PeopleWebService
    {
        [WebGet(UriTemplate = "")]
        public List<Person> GetCollection()
        {
            try
            {
                IPeopleRepository repository = ServiceLocator.GetInstance<IPeopleRepository>();
                var people = repository.GetPeople();
                // use automapper to map entities to Person resource
                var result = Mapper.Map<List<Person>>(people);
                return result;
            }
            catch (Exception exception)
            {
                // do logging etc
                throw new WebFaultException(HttpStatusCode.InternalError);
            }
        }
    
        /* other methods */
    }
    

    These services can be generated by T4 too.

    Notice that there is no need for an interface on the WCF service itself. I generally do not expose any database entities directly in WCF services as my services evolve differently than my database entities. Once an API is published it should pretty much remain the same. This prevents me from changing my database schema to fit new requirements.

    Instead I map my entities to resources. So Person may looks as follows:

    [DataContract]
    public class Person
    {
        [DataMember]
        public string GivenName { get; set; }
    
        / * more properties */
    }
    

    It may be a good thing to use T4 to generate these as well. Routing is defined something like this:

    public void Register(RouteCollection routes)
    {
        routes.AddService<WorkspaceWebService>("api/v1/people");
    }
    

    To consume it from the ASP.NET MVC project, you can share your resources (aka Person) as defined above as an assembly, or you can use T4 to generate a separate set of resources that are almost the same, but with some additional attributes needed for ASP.NET MVC, like those used for validation. I would generate it, because my ASP.NET MVC view models generally evolve independently to my REST resources.

    Lets assume your REST service runs at https://api.example.com/ and your MVC website is running at https://www.example.com/. Your PeopleController may look as follows.

    public class PeopleController : ControllerBase
    {
        [HttpGet]
        public ActionResult Index()
        {
            return View(Get<List<Person>>(new Uri("https://api.example.com/api/v1/people")));
        }
    
        protected T Get<T>(Uri uri)
        {
            var request = (HttpWebRequest) WebRequest.Create(uri);
            request.Method = "GET";
            request.ContentType = "text/xml";
            using (var response = (HttpWebResponse) request.GetResponse())
            {
                using (var responseStream = response.GetResponseStream())
                {
                    Debug.Assert(responseStream != null, "responseStream != null");
                    var serializer = new DataContractSerializer(typeof (T));
                    return (T) serializer.ReadObject(responseStream);
                }
            }
        }
    }
    

    From your question, I assume you want to use JSON. For that you just have to set the appropiate ContentType on the request and use the DataContractJsonSerializer rather than DataContractSeralizer. Note that there are some issues with dates and DataContractJsonSerializer. The WCF rest service will automatically return XML if the contenttype is "text/xml" and JSON if it is "application/json".

    Notice that the MVC application has no knowledge of the database, the database entities or its database context. In fact there is no database logic in the MVC application. You will have to pay close attention to security, because the user context is missing from the WCF rest services. But, that is a whole different discussion.