Friday, September 20, 2013

Steps to setup Apache reverse proxy on Window 7

1. Download Apache web server for Windows binary and install it.
2. In httpd.conf, update the listening port number
Listen 7000
3. uncomment the following lines to load the required modules
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule ssl_module modules/mod_ssl.so
4. enable virtual host by uncomment the following line
Include conf/extra/httpd-vhosts.conf
5. Edit : conf\httpd-vhosts.conf, for example, the following settings can be used to redirect request
from
torn004613401a.dhcp.tor2.sap.corp:7000/proxy/entity/odata
to
torn00461340a.dhcp.tor2.sap.corp/entity/odata, 


NameVirtualHost *:7000
<VirtualHost *:7000>
    ServerName torn00461340a.dhcp.tor2.sap.corp
    ServerAlias dymmyhost
    ErrorLog "logs/dummy-host.dhcp.tor2.sap.corp-error.log"
    CustomLog "logs/dummy-host.dhcp.tor2.sap.corp-access.log" common
    ProxyPass /proxy/ http://torn00461340a.dhcp.tor2.sap.corp/
    ProxyPassReverse /proxy/ http://torn00461340a.dhcp.tor2.sap.corp/

</VirtualHost>

Note extra steps are required to handle cookie path and domain and https connection.

Wednesday, September 4, 2013

Steps to create a CRUD oData service with ASP.Net Web API

Quite a lot of ASP.Net Web API shows how to create a read-only oData service, which is not very useful in real project, the following steps can be used to create a full CRUD webapi odata service that can run on iis. The project is inspired by pluralsight's ASP.Net oData training project as well as other online help information.

Tools:
VS 2012 on Windows 7

1. Create a new MVC 4 project
2. Add Entity Framework and import Northwind database, use Windows integrated mode for database connection
3. Add WebApi oData package using NeGet, the ApiController does not work with circular json serialize, so it has to use EntitySetController to create controller class.
4. update WebApiConfig.cs as follows
        public static void Register(HttpConfiguration config)
        {
            config.Routes.MapODataRoute("Northwind", "odata", GetImplicitEDM());

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
           ....
        }

        private static IEdmModel GetImplicitEDM()
        {
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            var customers = builder.EntitySet<Customer>("Customers");
            builder.EntitySet<Order>("Orders");
            builder.EntitySet<Shipper>("Shippers");
            return builder.GetEdmModel();
        }
    }

5. create a new CustomersController class, the name needs matching the EntitySet name.
public class CustomersController : EntitySetController<Customer, string>
{
   NORTHWNDEntities _context = new NORTHWNDEntities();

        [Queryable]
        public override IQueryable<Customer> Get()
        {
            return _context.Customers;
        }

        protected override Customer GetEntityByKey(string key)
        {
            return _context.Customers.FirstOrDefault(c => c.CustomerID == key);
        }

        [Queryable]
        public IQueryable<Order> GetOrdersFromCustomer([FromODataUri] string key)
        {
            return _context.Orders.Where(o => o.CustomerID == key);
        }

        protected override string GetKey(Customer entity)
        {
            return entity.CustomerID;
        }

        //sample request form fiddler
        //header
        //Content-Type: application/json
        //payload
        //{"CustomerID":"ABCDE","CompanyName":"ABCDE","ContactName":"ABCDE","ContactTitle":"ABCDE"}
        protected override Customer CreateEntity(Customer entity)
        {
            _context.Customers.Add(entity);
            _context.SaveChanges();
            return entity;
        }

        //sample request from fiddler, only specified fields from client will be set to the new objects, all other
        //fields will be set to null
        //url
        // put for http://localhost:58317/odata/Customers('ABCDE')
        //header
        //Content-Type: application/json
        //payload, customerid field will be ignored
        //{"CustomerID":"AAAA","CompanyName":"AAAA"}
        protected override Customer UpdateEntity(string key, Customer update)
        {
            if (!_context.Customers.Any(c => c.CustomerID == key))
            {
                throw new HttpResponseException(
                    Request.CreateODataErrorResponse(
                      HttpStatusCode.NotFound,
                      new ODataError
                      {
                         ErrorCode = "EntityNotFound",
                         Message = "Customer key " + key + " not found"
                      }));
            }

            update.CustomerID = key;
            _context.Customers.Attach(update);
            _context.Entry(update).State = System.Data.EntityState.Modified;
            _context.SaveChanges();
            return update;
        }

        //Similar to post but keeping the existing fields in object if client side does not specify them
        protected override Customer PatchEntity(string key, Delta<Customer> patch)
        {
         
            var customer = _context.Customers.FirstOrDefault(c => c.CustomerID == key);

            if (customer == null)
            {
                throw new HttpResponseException(
                    Request.CreateODataErrorResponse(
                      HttpStatusCode.NotFound,
                      new ODataError
                      {
                          ErrorCode = "EntityNotFound",
                          Message = "Customer key " + key + " not found"
                      }));
            }

            patch.Patch(customer);
            _context.SaveChanges();
            return customer;
        }

        //sample request from fiddler, only specified fields from client will be set to the new objects, all other
        //fields will be set to null
        //url
        //delete for http://localhost:58317/odata/Customers('ABCDE')
        //header
        //Content-Type: application/json
        public override void Delete([FromODataUri] string key)
        {

            var customer = _context.Customers.FirstOrDefault(c => c.CustomerID == key);

            if (customer == null)
            {
                throw new HttpResponseException(
                    Request.CreateODataErrorResponse(
                      HttpStatusCode.NotFound,
                      new ODataError
                      {
                          ErrorCode = "EntityNotFound",
                          Message = "Customer key " + key + " not found"
                      }));
            }
            _context.Customers.Remove(customer);
            _context.SaveChanges();
        }


        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            _context.Dispose();
        }
}

6. Start the app on IIS express, it should handle the oData request. But it will fail when running on IIS, continue the following steps to run it on IIS
7.Restart VS 2012 in admin mode, and change the project setting to create IIS virtual directory and use IIS for testing
8. Edit C:\Windows\System32\inetsrv\config\applicationHost.config for the following part
<add name="ASP.NET v4.0" autoStart="true" managedRuntimeVersion="v4.0" managedPipelineMode="Integrated">
<processModel identityType="ApplicationPoolIdentity" loadUserProfile="true" setProfileEnvironment="true" /></add>

(refer to http://blogs.msdn.com/b/sqlexpress/archive/2011/12/09/using-localdb-with-full-iis-part-1-user-profile.aspx and http://blogs.msdn.com/b/sqlexpress/archive/2011/12/09/using-localdb-with-full-iis-part-2-instance-ownership.aspx for details if you still get database connection error).

Tuesday, September 3, 2013

Some note about Linq, WCF, EntityFramework, WebApi, oData

The key to understand the syntax of linq, is it is built based on IEnumerable, so everything is based on a single element from the IEnumerable collection, if you see a variable declaration coming out of nowhere, then you can think of it as a instance of a IEnumerable instance. This is particular useful to understand Lambda syntax

WCF provides an easy way to implemenat oData service, but it is used mainly for .net client. If you create a oData service to support ios, Android devices, then it is better to create the oData service with WebApi, developer has better control over web API then WCF's customization.

EntityFramework helps to expose the backend data as objects, but when using WebApi, it is not very stable to serialize the dependent objects included in the foreign key field, the easy solution is just returning the foreign key field as a simple field data instead of returning it as a child  object. As for mobile client, it is not proper to return all objects included in object mapping, if client needs dependent objects, it is better to let client send another separate request.

When using EntitySetController to build oData endpoint, be sure the controller name matches the entity name, otherwise the resource cannot be found:

  private static IEdmModel GetImplicitEDM()
        {
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet<Customer>("Customers");
            builder.EntitySet<Order>("Orders");
            builder.EntitySet<Order_Detail>("OrderDetails");
            return builder.GetEdmModel();
        }

public class CustomersController : EntitySetController<Customer, string>
   

Fiddler: Set username and password for basic authentication

1. open fiddler, click Tools->TextWizard... menu item,
2. select checkbox of "To Base64"
3. input your user name and password in top textbox such as
myUsername:myPassword
4. Go back to fiddler composer screen and add a header of below, the last past is the output of
Authorization: Basic bXlVc2VybmFtZTpteVBhc3N3b3Jk