Click here to Skip to main content
12,298,085 members (53,138 online)
Click here to Skip to main content
Add your own
alternative version

Stats

30.1K views
25 bookmarked
Posted

Creating a Proof of Concept Web API App that Uses SQL Server in 25 Granular Steps

, 23 Jan 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
How to create a Web API IoC/DI App that Uses SQL Server

Jump Right In

So you wanna build a Proof Of Concept ASP.NET Web API REST app using SQL Server as your data store? Well, you've come to the right place, as we're going to dive right in, and move through this in step-by-step fashion.

  1. Download the Adventure Works Database. As this is a PoC and not some gigantic datahead extravaganza, I just download the LT (light) database, namely the "AdventureWorksLT2012_Database.zip" file, with less data and a less complex group of tables.
     
  2. Decompress the .zip file, placing the database files in a location known only to you, your mother, and the NSA (probably something like C:\YourNannyWoreCombatBootsWeekends\AdventureWorksLT2012_Database \AdventureWorksLT2012_Data.mdf)
     
  3. In Visual Studio 2013, select View > Server Explorer
  4. Right-click "Data Connections" and select "Add Connection..."
     
  5. From the "Choose Data Source" dialog, select "Microsoft SQL Server Database File"
  6. Mash the "Continue" button
     
  7. In the "Add Connection" dialog:
    1. Select the "Browse" button
    2. Navigate to the location where you saved the database file
    3. Select it
  8. To keep it as simple as possible, accept the default option for logging onto the server, "Use Windows Authentication"
     
  9. Mash the "Test Connection" button. If it fails, go into The Who mode, trashing your cubicle and its contents, and thrashing anyone who dares peek over the divider to see what all the commotion is about. If the connection succeeds ("shucks!"), go to step 9.
  10. Mash the "OK" button
     
  11. You will now see the AdventureWorks database listed below "Data Connections" in Server Explorer. You can expand the tables folder to see the tables, and the tables to see their columns. If you 2-click the Address table, it will eventually rise from the ether, not altogether unlike a genie from a bottle or a cobra from a basket, and divulge its design innards thus: 

To see the actual data, right-click the table in Server Explorer and select "Show Table Data"

Conditional Regression

If you don't already have an ASP.NET Web API IoC/DI project, build one using this article before returning back to here to add the SQL Server repository.

Onward, Coding Soldiers! : IKrieg, IGuerra

  1. Now that you've got that set up as well as possible, we'll pick up where we left off. Of course, the data query needs to be added, so let's do that now:
     
  2. Right-click your Models folder and select Add > Class... You could name it MisadventureWorks or something else that will raise the hackles of the tuxedo-garbed coding-seriousness cum "Professionalism" Nazis, but in this case I'll just name it "SQLServerPOC" (remember, POC stands for "Proof Of Concept" - not "Pterodactyls Over Canada"!).

    In this simple example, we're going to only deal with a few columns, so the Model will just contain a few - some from Address and some from Customer (related to each other via CustomerID and AddressID - AddressID in the Address table links to the CustomerAddress table, and the CustomerAddress table links to the Customer table).

    So that we don't get bogged down in details, and this article doesn't become a book, we won't deal with class member decorations such as "required" or length mins and maxes and such. We simply go with plain old strings and one int to hold all this data:
    public class SQLServerPOC
    {
        public int CustomerID { get; set; }
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        public string LastName { get; set; }
        public string AddressLine1 { get; set; }
        public string AddressLine2 { get; set; }
        public string City { get; set; }
        public string StateProvince { get; set; }
        public string CountryRegion { get; set; }
        public string PostalCode { get; set; }
    }
    
    Will be able to assemble the data from three related tables in AdventureWorks: Customer, CustomerAddress, and Address
  3. Right-click the Models folder again, and this time select Add > Interface
     
  4. Name it "ISQLServerPOCRepository"
  5. Mark the interface public and give it some reasonable methods to implement so that it looks like this: 
  6. public interface ISQLServerPOCRepository
    {
        int GetCount();
        SQLServerPOC GetByCustomerId(int ID);
        IEnumerable<sqlserverpoc> GetByLastName(string LName);
        IEnumerable<sqlserverpoc> GetByCountry(string CountryName);
        IEnumerable<sqlserverpoc> GetByStateOrProvince(string StateOrProvince);
        IEnumerable<sqlserverpoc> GetByPostalCode(string PostalCode);
        IEnumerable<sqlserverpoc> GetInPostalCodeRange(int PCBegin, int PCEnd);
        IEnumerable<sqlserverpoc> GetAll();
        SQLServerPOC Add(SQLServerPOC item);
    }
    
  7. Again, right-click the Models folder, this time selecting Add > Class...
  8. Name it "SQLServerPOCRepository"
  9. Add code so that the concrete repository class implements the corresponding interface (ISQLServerPOCRepository) and then add the interface methods by right-clicking the interface name and selecting "implement interface"

    You should see something like this:

    public class SQLServerPOCRepository : ISQLServerPOCRepository
    {
        public int GetCount()
        {
            throw new NotImplementedException();
        }
    
        public SQLServerPOC GetByCustomerId(int ID)
        {
            throw new NotImplementedException();
        }
    
        public IEnumerable<sqlserverpoc> GetByLastName(string LName)
        {
            throw new NotImplementedException();
        }
    
        public IEnumerable<sqlserverpoc> GetByCountry(string CountryName)
        {
            throw new NotImplementedException();
        }
    
        public IEnumerable<sqlserverpoc> GetByStateOrProvince(string StateOrProvince)
        {
            throw new NotImplementedException();
        }
    
        public IEnumerable<sqlserverpoc> GetByPostalCode(string PostalCode)
        {
            throw new NotImplementedException();
        }
    
        public IEnumerable<sqlserverpoc> GetInPostalCodeRange(int PCBegin, int PCEnd)
        {
            throw new NotImplementedException();
        }
    
        public IEnumerable<sqlserverpoc> GetAll()
        {
            throw new NotImplementedException();
        }
    
        public SQLServerPOC Add(SQLServerPOC item)
        {
            throw new NotImplementedException();
        }
    }
    
  10. Add a generic List variable so you will have a container for the data we will query:
    private readonly List<sqlserverpoc> customerAddressData = new List<sqlserverpoc>();
  11. Since it compiles (that's all I care about*), we'll leave the database code for later and add the Controller now.

    * Psyche! Or should it be... psycho!!!

  12. Right-click on your Controllers folder (your Web API project does have a Controllers folder, doesn't it? - if not, add one, or one named "api" or whatever works for you), and select Add > Controller... > Web API 2 Controller - Empty.

    Name it "SQLServerPOCController"

    While it's creating itself, try not to have any daymares about falling from a highrise in New York or some other godawful burg while washing windows.

    Add the IoC/DI/Castle Windsor-esque repository variable and interface-arg'd constructor:

    private readonly ISQLServerPOCRepository _SQLServerPOCRepository;
    
        public PeepsController(ISQLServerPOCRepository SQLServerPOCRepository)
        {
            if (SQLServerPOCRepository == null)
            {
                throw new ArgumentNullException("SQLServerPOCRepository is null!");
            }
            _SQLServerPOCRepository = SQLServerPOCRepository;
        }
    
  13. Add the rest of the Controller code, corresponding with the Repository methods you added:

    Since the Castle Windsor Framework, along with the Attribute Routings on the methods, are determining which URI invokes which method in the Controller (which in turn gets the data from the Repository), you could name these methods anything you want - instead of GetCountOfSQLServerPOCRecords(), you could have name that method GetShorty(), or GetBackToWhereYouOnceBelonged() or just about anything else. Not a good idea, though! Such tomfoolishness should be left to Congress; remember the adage: "Becoming a father is easy; *being* a father is difficult." IOW (as it applies here) you are the father (or mother) of your code, and to take care of it later, you need all the help you can get without giving in to some fanciful and fleeting whimsy of the moment (such as giving the methods non-intuitive names).

    You will note, though, that I kind of mixed and matched the usage of fully named routes (such as "api/SQLServerPOC/Count") and argument count and type routing (such as "api/SQLServerPOC/{ID:int}"). You can do either one. Just note that if you have two methods that take the same count and type of arguments, you will have to use the naming on at least one of them, so the routing engine can know which one to choose when it saves a URI that matches multiple methods.

    For example, since I have several methods that take a single string argument, they have to be differentiated from each other, as in:

    [Route("api/SQLServerPOC/GetByLastName/{LName}")]
    
    ...and:
    [Route("api/SQLServerPOC/GetByCountry/{Country}")]
    

    ...whereas the GetByCustomerID() method doesn't need to proclaim their uniqueness via an added identifier in the routing string, as the first is the only method with a single int as an arg, and the second is the only method with three strings for args.

    Here are all the Controller methods:

            [Route("api/SQLServerPOC/Count")]
            public int GetCountOfSQLServerPOCRecords()
            {
                return _SQLServerPOCRepository.GetCount();
            }
    
            [Route("api/SQLServerPOC/GetAll")]
            public IEnumerable<sqlserverpoc> GetAllSQLServerPOC()
            {
                return _SQLServerPOCRepository.GetAll();
            }
    
            [Route("api/SQLServerPOC/{ID:int}")]
            public SQLServerPOC GetSQLServerPOCByCustomerId(int ID)
            {
                return _SQLServerPOCRepository.GetByCustomerId(ID);
            }
    
            [Route("api/SQLServerPOC/GetByLastName/{LName}")]
            public IEnumerable<sqlserverpoc> GetSQLServerPOCByLastName(string LName)
            {
                return _SQLServerPOCRepository.GetByLastName(LName);
            }
    
            [Route("api/SQLServerPOC/GetByCountry/{Country}")]
            public IEnumerable<sqlserverpoc> GetSQLServerPOCByCountry(string Country)
            {
                return _SQLServerPOCRepository.GetByCountry(Country);
            }
    
            [Route("api/SQLServerPOC/GetByStateOrProvince/{StateOrProvince}")]
            public IEnumerable<sqlserverpoc> GetSQLServerPOCByStateOrProvince(string StateOrProvince)
            {
                return _SQLServerPOCRepository.GetByStateOrProvince(StateOrProvince);
            }
    
            [Route("api/SQLServerPOC/GetByPostalCode/{PostalCode}")]
            public IEnumerable<sqlserverpoc> GetSQLServerPOCByPostalCode(string PostalCode)
            {
                return _SQLServerPOCRepository.GetByPostalCode(PostalCode);
            }
    </sqlserverpoc>
  14. Okay, we're getting pretty close to showtime. Two more things to add: a line of code to let the Castle Windsor routing engine know which concrete class to instantiate/grab data from for the controller that implements the interface we added, and the code to query the SQL Server database (SQL) and then the Repository's methods that query that query using LINQ.

    In RepositoriesInstaller (which will exist beneath the DIInstallers folder if you played along with the Web API IoC/DI Castle Windsor tutorial referenced earlier), add a line like so:

    . . .
    Component.For<<isqlserverpocrepository>().ImplementedBy<sqlserverpocrepository>().LifestylePerWebRequest(),
    . . .
    

    That will register SQLServerPOCRepository as the class that implements ISQLServerPOCRepository that should be used when Castle Windsor routes to the SQLServerPOCController.

    24) Now, the crux of the biscuit, the Pièce de résistance*, the fulcrum of the whole shebang: the database code. Add this constructor in the SQLServerPOCRepository:

            public SQLServerPOCRepository()
            {
                const int CUSTOMERID_OFFSET = 0;
                const int FIRSTNAME_OFFSET = 1;
                const int MIDDLENAME_OFFSET = 2;
                const int LASTNAME_OFFSET = 3;
                const int ADDRESS1_OFFSET = 4;
                const int ADDRESS2_OFFSET = 5;
                const int CITY_OFFSET = 6;
                const int STATE_OFFSET = 7;
                const int ZIP_OFFSET = 8;
                const int COUNTRY_OFFSET = 9;
    
                // Values that may be null are "special" and have to be checked for null to prevent a minor explosion
                string address2 = string.Empty;
                string middleName = string.Empty;
    
                using (var conn = new SqlConnection(
                    @"Data Source=(LocalDb)\v11.0;AttachDBFilename=C:\HoldingTank\AdventureWorksLT2012_Database
    
    \AdventureWorksLT2012_Data.MDF;Integrated Security=True;"))
                {
                    using (var cmd = conn.CreateCommand())
                    {
                        cmd.CommandText = 
    		@"SELECT C.CustomerID, C.FirstName, C.MiddleName, C.LastName, 
                                                A.AddressLine1, A.AddressLine2, A.City, A.StateProvince, 
                                                A.PostalCode, A.CountryRegion 
                                            FROM  SalesLT.CustomerAddress U 
                                                INNER JOIN SalesLT.Address A ON A.AddressID = U.AddressID
                                                INNER JOIN  SalesLT.Customer C ON U.CustomerID = C.CustomerID  
                                            ORDER BY C.LastName, C.FirstName";
                        cmd.CommandType = CommandType.Text;
                        conn.Open();
                        using (SqlDataReader sqlD8aReader = cmd.ExecuteReader())
                        {
                            while (sqlD8aReader != null && sqlD8aReader.Read())
                            {
                                int custID = sqlD8aReader.GetInt32(CUSTOMERID_OFFSET);
                                string firstName = sqlD8aReader.GetString(FIRSTNAME_OFFSET);
                                if (!sqlD8aReader.IsDBNull(MIDDLENAME_OFFSET))
                                {
                                    middleName = sqlD8aReader.GetString(MIDDLENAME_OFFSET);
                                }
                                string lastName = sqlD8aReader.GetString(LASTNAME_OFFSET);
                                string address1 = sqlD8aReader.GetString(ADDRESS1_OFFSET);
                                if (!sqlD8aReader.IsDBNull(ADDRESS2_OFFSET))
                                {
                                    address2 = sqlD8aReader.GetString(ADDRESS2_OFFSET);
                                }
                                string city = sqlD8aReader.GetString(CITY_OFFSET);
                                string stateOrProvince = sqlD8aReader.GetString(STATE_OFFSET);
                                string postalCode = sqlD8aReader.GetString(ZIP_OFFSET);
                                string country = sqlD8aReader.GetString(COUNTRY_OFFSET);                            
    	       Add(new SQLServerPOC { CustomerID = custID, FirstName = firstName, MiddleName = middleName, LastName = 	
    
                lastName, AddressLine1 = address1, AddressLine2 = address2, City = city, StateProvince = stateOrProvince, 
                                    PostalCode = postalCode, CountryRegion = country });
                            }
                        }
                    }
                }
            }
    

    Note: If you want to test your query before running your app, so as to tweak it until its right and see the data returned to verify you're getting what you expect, I recommend using the free LINQPad, which you can download here

    .

    It should be pretty easy to suss out how to use it - Add a Connection using the default LINQtoSQL driver, point out to your database, select SQL as your language, and then enter the SQL query.

    For example, here's what I saw in LINQPad once I got my query right:

    * I know, that phrase doesn't mean what I seem to think it means, but it sounds like what I'm looking for and, as this is an aural medium (you're not *reading* this, are you?!?) I'm going with what sounds good.

  15. Now we use LINQ to get just the desired data from the generic list that was populated. The Repository methods thus become:

    public int GetCount()
    {
        return customerData.Count;
    }
    
    public SQLServerPOC GetByCustomerId(int ID)
    {
        return customerData.FirstOrDefault(c => c.CustomerID == ID);
    }
    
    public IEnumerable<sqlserverpoc> GetByLastName(string LName)
    {
        return customerData.Where(c => c.LastName == LName);
    }
    
    public IEnumerable<sqlserverpoc> GetByCountry(string CountryName)
    {
        return customerData.Where(c => c.CountryRegion == CountryName);
    }
    
    public IEnumerable<sqlserverpoc> GetByStateOrProvince(string StateOrProvince)
    {
        return customerData.Where(c => c.StateProvince == StateOrProvince);
    }
    
    public IEnumerable<sqlserverpoc> GetByPostalCode(string PostalCode)
    {
        return customerData.Where(c => c.PostalCode == PostalCode);
    }
    
    public IEnumerable<sqlserverpoc> GetAll()
    {
        return customerData;
    }
    
    public SQLServerPOC Add(SQLServerPOC item)
    {
        if (item == null)
        {
            throw new ArgumentNullException("item arg was null");
        }
        if (customerData != null) customerData.Add(item);
        return item;
    }
    

Running the app and then entering the appropriate URI in the browser produces data in XML in Chrome:

If the browser XML is too ugly for you, you can easily write a Windows forms util to test your REST methods. Some cat wrote an article on how to do that here

Here's what such a util might look like this for the data in this article, when filtering by country and selecting "United Kingdom":

The code in the Windows forms util behind that particular (by country) query is:

        private void buttonAdvWorksCountry_Click(object sender, EventArgs e)
        {
            string country = comboBoxAdvWorksCountry.SelectedItem.ToString();
            string uri = string.Format("sqlserverpoc/GetByCountry/{0}", country);
            Popul8TheGrid(uri);
        }

        private void Popul8TheGrid(string uri)
        {
            try
            {
                dataGridView1.DataSource = GetRESTData(BASE_URI + uri);
            }
            catch (WebException webex)
            {
                MessageBox.Show("Eek, a mousey-pooh! ({0})", webex.Message);
            }
        }

        // Uses Newtonsoft's JSON.NET
        private JArray GetRESTData(string uri)
        {
            var webRequest = (HttpWebRequest) WebRequest.Create(uri);
            var webResponse = (HttpWebResponse) webRequest.GetResponse();
            var reader = new StreamReader(webResponse.GetResponseStream());
            string s = reader.ReadToEnd();
            return JsonConvert.DeserializeObject<jarray>(s);
        }
</jarray>

Later Daze

So that should be all you need to start using SQL Server data as REST data that can be consumed in the client of your delight (browser, Windows forms app, WPF app, whatever).

If you like this article, get up and go outside and take your dog for a walk; if you don't have a dog, walk a neighbor's dog, or their Duckbilled Platypus* - but watch out for the poisonous toe on his hind foot!

*Whereas you can call the dog "Spot" or "Rover" if you don't know or remember his name, a better bet for Duckbilled Platypi is "Tiglath-Platypeser" which, AFAIK, is the most common name for pets of that species.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

B. Clay Shannon
Founder "Across Time & Space"
United States United States
Ideaman and Coder at Across Time & Space, creator of "Mark Twain Central" at http://twaincentral.azurewebsites.net/

Peripatetic and picaresque, I have lived in eight states; specifically, besides my native California (where I was born and where I now again reside) in chronological order: New York, Montana, Alaska, Oklahoma, Wisconsin, Idaho, and Missouri.

I am also a writer of both fiction (for which I use a nom de plume, "Blackbird Crow Raven", as a nod to my Native American heritage - I am "½ Cowboy, ½ Indian") and nonfiction: http://www.lulu.com/spotlight/blackbirdcraven

You may also be interested in...

Comments and Discussions

 
QuestionStep 24. what does customerData reference? Pin
regeter5-Jan-15 15:37
memberregeter5-Jan-15 15:37 
QuestionHow would you add Counts? Pin
smoore428-Nov-14 6:06
membersmoore428-Nov-14 6:06 
QuestionIncomplete Pin
Member 1109233318-Sep-14 3:59
memberMember 1109233318-Sep-14 3:59 
AnswerRe: Incomplete Pin
regeter5-Jan-15 15:33
memberregeter5-Jan-15 15:33 
SuggestionSome improvements needed, IMO Pin
JarmoM1-Feb-14 5:12
memberJarmoM1-Feb-14 5:12 
GeneralRe: Some improvements needed, IMO Pin
B. Clay Shannon1-Feb-14 5:19
professionalB. Clay Shannon1-Feb-14 5:19 
GeneralRe: Some improvements needed, IMO Pin
JarmoM1-Feb-14 21:10
memberJarmoM1-Feb-14 21:10 
GeneralRe: Some improvements needed, IMO Pin
B. Clay Shannon2-Feb-14 4:31
professionalB. Clay Shannon2-Feb-14 4:31 
GeneralRe: Some improvements needed, IMO Pin
B. Clay Shannon3-Feb-14 7:35
professionalB. Clay Shannon3-Feb-14 7:35 
GeneralRe: Some improvements needed, IMO Pin
B. Clay Shannon3-Feb-14 8:08
professionalB. Clay Shannon3-Feb-14 8:08 
GeneralRe: Some improvements needed, IMO Pin
B. Clay Shannon3-Feb-14 8:43
professionalB. Clay Shannon3-Feb-14 8:43 
QuestionFantasmisgorical! Pin
RB Starkey27-Jan-14 8:59
professionalRB Starkey27-Jan-14 8:59 
AnswerRe: Fantasmisgorical! Pin
B. Clay Shannon27-Jan-14 11:10
professionalB. Clay Shannon27-Jan-14 11:10 
QuestionAny chance to get the code? Pin
cmathews86425-Jan-14 17:26
membercmathews86425-Jan-14 17:26 
AnswerRe: Any chance to get the code? Pin
B. Clay Shannon25-Jan-14 18:36
professionalB. Clay Shannon25-Jan-14 18:36 
GeneralRe: Any chance to get the code? Pin
regeter5-Jan-15 14:02
memberregeter5-Jan-15 14:02 
Questionwhy not just use... Pin
Sacha Barber23-Jan-14 20:13
mvpSacha Barber23-Jan-14 20:13 
AnswerRe: why not just use... Pin
B. Clay Shannon24-Jan-14 5:08
professionalB. Clay Shannon24-Jan-14 5:08 
GeneralRe: why not just use... Pin
rlltech25-Jan-14 8:07
memberrlltech25-Jan-14 8:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160525.2 | Last Updated 23 Jan 2014
Article Copyright 2014 by B. Clay Shannon
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid