diff --git a/ClientConsole/App.config b/ClientConsole/App.config
new file mode 100644
index 00000000..09cc310f
--- /dev/null
+++ b/ClientConsole/App.config
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ClientConsole/ClientConsole.csproj b/ClientConsole/ClientConsole.csproj
new file mode 100644
index 00000000..96665ac9
--- /dev/null
+++ b/ClientConsole/ClientConsole.csproj
@@ -0,0 +1,65 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {CDB23559-BB93-4946-9802-9222A6586013}
+ Exe
+ ClientConsole
+ ClientConsole
+ v4.6.1
+ 512
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {F2472982-DC7E-4EB8-86DD-45F1E3D189E8}
+ SoftWriters.RestaurantReviews.DataLibrary
+
+
+ {4F38EB50-BB3A-44E7-9FE2-3600FCC0BE07}
+ SoftWriters.RestaurantReviews.WebApi
+
+
+
+
\ No newline at end of file
diff --git a/ClientConsole/Program.cs b/ClientConsole/Program.cs
new file mode 100644
index 00000000..684421fe
--- /dev/null
+++ b/ClientConsole/Program.cs
@@ -0,0 +1,178 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using SoftWriters.RestaurantReviews.DataLibrary;
+
+namespace ClientConsole
+{
+ // Since unit tests test the API pretty well, we only use this console client to make sure the plumbing
+ // is in place and functions correctly. We don't necessarily need complete functionality here but you can
+ // go ahead and add functionality as needed.
+ // NOTE: This is good for say, a working demo before UI gets created.
+ class Program
+ {
+ private static string Instructions =
+ "\nType the appropriate number that corresponds to the api call you would like to test, then press ENTER:" +
+ "\n\t0\tExit" +
+ "\n\t1\tGet Restaurants By City" +
+ "\n\t2\tGet Restaurants By Zip" +
+ "\n\t3\tGet All Restaurants" +
+ "\n\t4\tAdd Restaurant" +
+ "\n\t5\tAdd Review" +
+ "\n\t6\tGet Reviews by user" +
+ "\n\t7\tGet Reviews by restaurant";
+
+ // We don't have any concept of logging in, so this will represent the current user
+ private static Guid UserId = Guid.Parse("611C77C4-DA99-4674-8252-87C9923A47D3");
+
+ static void Main(string[] args)
+ {
+ var reviewClient = new ReviewClient();
+ Console.WriteLine("Connected!\n");
+ Console.WriteLine(Instructions);
+
+ while (true)
+ {
+ var input = Console.ReadLine().Trim().ToLower();
+
+ switch (input)
+ {
+ case "0":
+ case "q":
+ reviewClient.Close();
+ Environment.Exit(0);
+ break;
+
+ case "1":
+ GetRestaurantsByCity(reviewClient);
+ break;
+
+ case "2":
+ GetRestaurantsByZip(reviewClient);
+ break;
+
+ case "3":
+ GetAllRestaurants(reviewClient);
+ break;
+
+ case "4":
+ AddRestaurant(reviewClient);
+ break;
+
+ case "5":
+ AddReview(reviewClient);
+ break;
+
+ default:
+ Console.WriteLine("Invalid input");
+ break;
+ }
+
+ Console.WriteLine(Instructions);
+ Console.Write(":");
+ }
+ }
+
+ static void GetRestaurantsByCity(ReviewClient reviewClient)
+ {
+ Console.Write("Enter city: ");
+ var city = Console.ReadLine();
+ Console.WriteLine();
+ var restaurants = reviewClient.GetRestaurants("", city, "", "", "");
+ PrintRestaurants(restaurants);
+ }
+
+ static void GetRestaurantsByZip(ReviewClient reviewClient)
+ {
+ Console.Write("Enter zip code: ");
+ var postalCode = Console.ReadLine();
+ Console.WriteLine();
+ var restaurants = reviewClient.GetRestaurants("", "", "", postalCode, "");
+ PrintRestaurants(restaurants);
+ }
+
+ static void GetAllRestaurants(ReviewClient reviewClient)
+ {
+ Console.WriteLine("Get all restaurants");
+ var restaurants = reviewClient.GetRestaurants("", "", "", "", "");
+ PrintRestaurants(restaurants);
+ }
+
+ static void AddRestaurant(ReviewClient reviewClient)
+ {
+ Console.Write("Enter restaurant name: ");
+ var restaurantName = Console.ReadLine();
+ Console.Write("\nEnter street address: ");
+ var restaurantStreet = Console.ReadLine();
+ Console.Write("\nEnter city: ");
+ var restaurantCity = Console.ReadLine();
+ Console.Write("\nEnter state: ");
+ var restaurantState = Console.ReadLine();
+ Console.Write("\nEnter postal code: ");
+ var restaurantZip = Console.ReadLine();
+ Console.Write("\nEnter country: ");
+ var restaurantCountry = Console.ReadLine();
+
+ bool result = reviewClient.AddRestaurant(restaurantName, restaurantStreet, restaurantCity, restaurantState,
+ restaurantZip, restaurantCountry);
+
+ Console.WriteLine("\n{0}", result ? "Add successful" : "Add failed");
+ }
+
+ static void AddReview(ReviewClient reviewClient)
+ {
+ Console.WriteLine("Enter the number corresponding to ");
+
+ var restaurants = reviewClient.GetRestaurants("", "", "", "", "").ToList();
+
+ TrySelectRestaurant(restaurants, out Restaurant restaurant);
+ Console.Write("\nEnter overall rating (1-5): ");
+ var overallRating = int.TryParse(Console.ReadLine(), out int oRating) ? oRating : 0;
+
+ Console.Write("\nEnter food rating (1-5): ");
+ var foodRating = int.TryParse(Console.ReadLine(), out int fRating) ? fRating : 0;
+
+ Console.Write("\nEnter service rating (1-5): ");
+ var serviceRating = int.TryParse(Console.ReadLine(), out int sRating) ? sRating : 0;
+
+ Console.Write("\nEnter cost rating (1-5): ");
+ var costRating = int.TryParse(Console.ReadLine(), out int cRating) ? cRating : 0;
+
+ Console.Write("Enter comments: ");
+ var comments = Console.ReadLine();
+
+ bool result = reviewClient.AddReview(UserId, restaurant.Id, overallRating, foodRating, serviceRating, costRating, comments);
+
+ Console.WriteLine("\n{0}", result ? "Add successful" : "Add failed");
+ }
+
+ static bool TrySelectRestaurant(List restaurants, out Restaurant selection)
+ {
+ selection = restaurants[0];
+
+ Console.WriteLine("Select restaurant (type the number and press Enter)");
+ for (int i = 0; i < restaurants.Count; i++)
+ {
+ Console.WriteLine("[{0}]\t{1}", i, restaurants[i].Name);
+ }
+
+ var input = Console.ReadLine();
+ if (!int.TryParse(input, out int index))
+ return false;
+
+ if (index >= restaurants.Count)
+ return false;
+
+ selection = restaurants[index];
+ return true;
+ }
+
+ static void PrintRestaurants(IEnumerable restaurants)
+ {
+ foreach (var item in restaurants)
+ {
+ Console.WriteLine(item.Name);
+ }
+ }
+ }
+}
diff --git a/ClientConsole/Properties/AssemblyInfo.cs b/ClientConsole/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..51e0f842
--- /dev/null
+++ b/ClientConsole/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ClientConsole")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ClientConsole")]
+[assembly: AssemblyCopyright("Copyright © 2021")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("cdb23559-bb93-4946-9802-9222a6586013")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/ClientConsole/ReviewClient.cs b/ClientConsole/ReviewClient.cs
new file mode 100644
index 00000000..a90c0018
--- /dev/null
+++ b/ClientConsole/ReviewClient.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.ServiceModel;
+using System.ServiceModel.Channels;
+using SoftWriters.RestaurantReviews.DataLibrary;
+using SoftWriters.RestaurantReviews.WebApi;
+
+namespace ClientConsole
+{
+ public class ReviewClient : ClientBase, IReviewApi
+ {
+ public ReviewClient()
+ { }
+
+ public ReviewClient(Binding binding, EndpointAddress remoteAddress) : base(binding, remoteAddress)
+ { }
+
+ public IEnumerable GetRestaurants(string street, string city, string stateCode, string postalCode, string country)
+ {
+ return Channel.GetRestaurants(street, city, stateCode, postalCode, country);
+ }
+
+ public IEnumerable GetReviews(Guid userId, Guid restaurantId)
+ {
+ return Channel.GetReviews(userId, restaurantId);
+ }
+
+ public bool AddRestaurant(string name, string street, string city, string stateCode, string postalCode, string country)
+ {
+ return Channel.AddRestaurant(name, street, city, stateCode, postalCode, country);
+ }
+
+ public bool AddReview(Guid userId, Guid restaurantId, int overallRating, int foodRating, int serviceRating, int costRating,
+ string comments)
+ {
+ return Channel.AddReview(userId, restaurantId, overallRating, foodRating, serviceRating, costRating,
+ comments);
+ }
+
+ public bool DeleteReview(Guid id)
+ {
+ return Channel.DeleteReview(id);
+ }
+ }
+}
diff --git a/CreateTestCerts.ps1 b/CreateTestCerts.ps1
new file mode 100644
index 00000000..a7f6f609
--- /dev/null
+++ b/CreateTestCerts.ps1
@@ -0,0 +1,25 @@
+# For creating self-signed test certificates
+# NOTE: Needs run as administrator
+# NOTE: $dnsName must match the host name of the server \ uri of the service
+
+$dnsName = "DESKTOP-JJLKN7I"
+$rootDnsName = "RootCA for " + $dnsName
+$outputDir = "C:\testCertificates\" + $dnsName + "\"
+$rootPfxPath = $outputDir + 'root.pfx'
+$rootCrtPath = $outputDir + 'root.crt'
+$pfxPath = $outputDir + 'cert.pfx'
+$crtPath = $outputDir + 'cert.crt'
+
+New-Item -Path $outputDir -ItemType Directory
+
+$rootCert = New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -DnsName $rootDnsName -FriendlyName "BryanRootCA" -NotAfter (Get-Date).AddYears(1) -KeyUsage CertSign
+[System.Security.SecureString]$rootcertPassword = ConvertTo-SecureString -String "password" -Force -AsPlainText
+[String]$rootCertPath = Join-Path -Path 'cert:\LocalMachine\My\' -ChildPath "$($rootcert.Thumbprint)"
+
+Export-PfxCertificate -Cert $rootCertPath -FilePath $rootPfxPath -Password $rootcertPassword
+Export-Certificate -Cert $rootCertPath -FilePath $rootCrtPath
+
+$testCert = New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -DnsName $dnsName -KeyExportPolicy Exportable -KeyLength 2048 -KeyUsage DigitalSignature,KeyEncipherment -Signer $rootCert
+[String]$testCertPath = Join-Path -Path 'cert:\LocalMachine\My\' -ChildPath "$($testCert.Thumbprint)"
+Export-PfxCertificate -Cert $testCertPath -FilePath $pfxPath -Password $rootcertPassword
+Export-Certificate -Cert $testCertPath -FilePath $crtPath
\ No newline at end of file
diff --git a/InstallHelper/CustomAction.config b/InstallHelper/CustomAction.config
new file mode 100644
index 00000000..c837a2ce
--- /dev/null
+++ b/InstallHelper/CustomAction.config
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/InstallHelper/CustomAction.cs b/InstallHelper/CustomAction.cs
new file mode 100644
index 00000000..4516ba76
--- /dev/null
+++ b/InstallHelper/CustomAction.cs
@@ -0,0 +1,45 @@
+using System;
+using System.IO;
+using Microsoft.Deployment.WindowsInstaller;
+
+namespace InstallHelper
+{
+ public class CustomActions
+ {
+ [CustomAction]
+ public static ActionResult UpdateDomainInConfigs(Session session)
+ {
+ session.Log("Begin UpdateDomainInConfigs");
+
+ try
+ {
+ // need to pass the data to a deferred custom action as a single property
+ // so here we are using an '*' to delimit the data
+ string data = session.CustomActionData["DOMAINCONFIGDATA"];
+ var items = data.Split('*');
+ var installFolder = items[0];
+
+ var domain = items[1];
+ var serviceConfig = Path.Combine(installFolder, "Service", "SoftWriters.RestaurantReviews.Service.exe.config");
+ var clientConfig = Path.Combine(installFolder, "TestClient", "ClientConsole.exe.config");
+
+ UpdateAppConfigWithDomain(serviceConfig, domain);
+ UpdateAppConfigWithDomain(clientConfig, domain);
+ }
+ catch (Exception e)
+ {
+ session.Log("Error updating app.config files with domain: " + e.Message);
+ return ActionResult.Failure;
+ }
+
+ return ActionResult.Success;
+ }
+
+ private static void UpdateAppConfigWithDomain(string filename, string domain)
+ {
+ var contents = File.ReadAllText(filename);
+ contents = contents.Replace("localhost", domain);
+ File.WriteAllText(filename, contents);
+ }
+ }
+}
diff --git a/InstallHelper/InstallHelper.csproj b/InstallHelper/InstallHelper.csproj
new file mode 100644
index 00000000..a8bc168b
--- /dev/null
+++ b/InstallHelper/InstallHelper.csproj
@@ -0,0 +1,54 @@
+
+
+
+ Debug
+ x86
+ 8.0.30703
+ 2.0
+ {68F6E2E8-E373-4D97-B26B-6D71B3A06FD2}
+ Library
+ Properties
+ InstallHelper
+ InstallHelper
+ v4.6.1
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/InstallHelper/Properties/AssemblyInfo.cs b/InstallHelper/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..9e848365
--- /dev/null
+++ b/InstallHelper/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("InstallHelper")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("InstallHelper")]
+[assembly: AssemblyCopyright("Copyright © 2021")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("68f6e2e8-e373-4d97-b26b-6d71b3a06fd2")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/README.txt b/README.txt
new file mode 100644
index 00000000..2c674001
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,53 @@
+ORGANIZATION *********************************************
+SoftWriters.RestaurantReviews.WebApi
+- This is the actual api and implementation
+
+SoftWriters.RestarantReviews.DataLibrary
+- This contains my data objects as well as my implementation for my disk based data stores.
+
+SoftWriters.RestaurantReviews.Service
+- Service to host the web api
+
+Test
+- Directory that contains the following:
+ - ServiceConsole: Service console, similar to my service, but runs in a console for ease of testing
+ - ClientConsole: A console app with a client implementation. Good for testing a client, similar to what would
+ be used in a mobile app.
+ - SoftWriters.RestaurantReviews.WebApi.Tests: My api unit tests
+ - CreateTestCerts.ps1: A powershell script to create self-signed test certs, needed for TLS\SSL configuration
+
+Installer
+- Contains a project for the msi and a custom action that updates the app.config files with the appropriate domain (passed via command line along with install folder)
+- To run installer: msiexec /i RestaurantReviewsServerMsi.msi DOMAIN= InstallerFolder=C:\some\Path /lvoicewarmupx installerlog.txt
+
+SETUP *********************************************
+- In order to run the test console apps, you need to Run As Administrator
+- If using TLS\SSL, you need to create the certificates, and the certificate must have the host name as the service
+- In the console client, just comment/uncomment the appropriate lines to toggle between http and https
+- NOTE: In the configs, I only have https endpoints commented out, because I wanted a working solution with an installer,
+and so I didn't want to have to worry about certificates. But I tested with https endpoints and it should be all good, if running the ServiceConsole
+
+DEPENDENCIES ******************************************
+- You will need to restore nuget packages to get the MSTest packages
+- For the installer, I have WiX v3.11 installed, and so if you don't have that installed, or the WiX VS extension
+just unload the projects under Installer and forget about them
+
+AREAS OF IMPROVEMENT *********************************************
+- I tried to make my api agnostic to the data source, and rather than connect to a database, for my testing, I created
+a disk based data store. However, there could probably be improvements. My datastore isn't very efficient. Also, if
+connecting to a proper database, my api could be tweaked a little, such as using ints for ids rather than guids.
+
+- My app doesn't really have a concept of a logged in user. Some calls I pass in a user id, and some I make an assumption
+about who the user is.
+
+- I created unit tests for my api, but not my xml data stores. As I said, if I were going via a database, I wouldn't be using
+these data stores. But, if I were going with the xml data stores in the long run, then I should probably have some unit tests
+created for those as well.
+
+- Once I got my API working, I thought I would be fancy and include and installer project, complete with install bootstrapper. I didn't create the installer UI
+because that would take a lot of time to do right and I wouldn't want to do it if it wasn't right. But if you're going to have the installer, it would be good
+to have a bootstrapper that defaults the domain to that of the target machine.
+
+- I targeted .NET 4.6.1, because I worked on a web api project recently that also targeted that .NET version, and so was most comfortable with that version.
+I believe I had read that in more recent .NET versions, they handle certain things a little differently. I can't remember what that is, but I would assume that
+if targeting a recent version of .NET Framework, one would have to do at least some minimal rework to get everything running properly.
\ No newline at end of file
diff --git a/RestaurantReviews.sln b/RestaurantReviews.sln
new file mode 100644
index 00000000..740beb32
--- /dev/null
+++ b/RestaurantReviews.sln
@@ -0,0 +1,86 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.28307.1401
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SoftWriters.RestaurantReviews.DataLibrary", "SoftWriters.RestaurantReviews.DataLibrary\SoftWriters.RestaurantReviews.DataLibrary.csproj", "{F2472982-DC7E-4EB8-86DD-45F1E3D189E8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SoftWriters.RestaurantReviews.Service", "SoftWriters.RestaurantReviews.Service\SoftWriters.RestaurantReviews.Service.csproj", "{E7FF0558-C73D-4F0A-B56C-E5C595A87E41}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SoftWriters.RestaurantReviews.WebApi", "SoftWriters.RestaurantReviews.WebApi\SoftWriters.RestaurantReviews.WebApi.csproj", "{4F38EB50-BB3A-44E7-9FE2-3600FCC0BE07}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceConsole", "ServiceConsole\ServiceConsole.csproj", "{C87A79FA-E477-4E8A-87A5-7DAB90F6F50A}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{1782B4F8-CF3F-4C00-B854-DF5302BFAEEB}"
+ ProjectSection(SolutionItems) = preProject
+ CreateTestCerts.ps1 = CreateTestCerts.ps1
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClientConsole", "ClientConsole\ClientConsole.csproj", "{CDB23559-BB93-4946-9802-9222A6586013}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B07E82E1-2671-4549-9F5E-2FC5C94E8835}"
+ ProjectSection(SolutionItems) = preProject
+ README.txt = README.txt
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Installer", "Installer", "{D4973E9C-9598-4511-8BBC-7F14936FFE10}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SoftWriters.RestaurantReviews.WebApi.Tests", "SoftWriters.RestaurantReviews.WebApi.Tests\SoftWriters.RestaurantReviews.WebApi.Tests.csproj", "{B36A5C8D-5C18-47E9-A3D9-070FD70706AD}"
+EndProject
+Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "RestaurantReviewsServerMsi", "RestaurantReviewsServerMsi\RestaurantReviewsServerMsi.wixproj", "{44924695-C006-4D5A-880B-530EE78B1860}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InstallHelper", "InstallHelper\InstallHelper.csproj", "{68F6E2E8-E373-4D97-B26B-6D71B3A06FD2}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {F2472982-DC7E-4EB8-86DD-45F1E3D189E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F2472982-DC7E-4EB8-86DD-45F1E3D189E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F2472982-DC7E-4EB8-86DD-45F1E3D189E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F2472982-DC7E-4EB8-86DD-45F1E3D189E8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E7FF0558-C73D-4F0A-B56C-E5C595A87E41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E7FF0558-C73D-4F0A-B56C-E5C595A87E41}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E7FF0558-C73D-4F0A-B56C-E5C595A87E41}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E7FF0558-C73D-4F0A-B56C-E5C595A87E41}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4F38EB50-BB3A-44E7-9FE2-3600FCC0BE07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4F38EB50-BB3A-44E7-9FE2-3600FCC0BE07}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4F38EB50-BB3A-44E7-9FE2-3600FCC0BE07}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4F38EB50-BB3A-44E7-9FE2-3600FCC0BE07}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C87A79FA-E477-4E8A-87A5-7DAB90F6F50A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C87A79FA-E477-4E8A-87A5-7DAB90F6F50A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C87A79FA-E477-4E8A-87A5-7DAB90F6F50A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C87A79FA-E477-4E8A-87A5-7DAB90F6F50A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CDB23559-BB93-4946-9802-9222A6586013}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CDB23559-BB93-4946-9802-9222A6586013}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CDB23559-BB93-4946-9802-9222A6586013}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CDB23559-BB93-4946-9802-9222A6586013}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B36A5C8D-5C18-47E9-A3D9-070FD70706AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B36A5C8D-5C18-47E9-A3D9-070FD70706AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B36A5C8D-5C18-47E9-A3D9-070FD70706AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B36A5C8D-5C18-47E9-A3D9-070FD70706AD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {44924695-C006-4D5A-880B-530EE78B1860}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {44924695-C006-4D5A-880B-530EE78B1860}.Debug|Any CPU.Build.0 = Debug|x86
+ {44924695-C006-4D5A-880B-530EE78B1860}.Release|Any CPU.ActiveCfg = Release|x86
+ {44924695-C006-4D5A-880B-530EE78B1860}.Release|Any CPU.Build.0 = Release|x86
+ {68F6E2E8-E373-4D97-B26B-6D71B3A06FD2}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {68F6E2E8-E373-4D97-B26B-6D71B3A06FD2}.Debug|Any CPU.Build.0 = Debug|x86
+ {68F6E2E8-E373-4D97-B26B-6D71B3A06FD2}.Release|Any CPU.ActiveCfg = Release|x86
+ {68F6E2E8-E373-4D97-B26B-6D71B3A06FD2}.Release|Any CPU.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {C87A79FA-E477-4E8A-87A5-7DAB90F6F50A} = {1782B4F8-CF3F-4C00-B854-DF5302BFAEEB}
+ {CDB23559-BB93-4946-9802-9222A6586013} = {1782B4F8-CF3F-4C00-B854-DF5302BFAEEB}
+ {B36A5C8D-5C18-47E9-A3D9-070FD70706AD} = {1782B4F8-CF3F-4C00-B854-DF5302BFAEEB}
+ {44924695-C006-4D5A-880B-530EE78B1860} = {D4973E9C-9598-4511-8BBC-7F14936FFE10}
+ {68F6E2E8-E373-4D97-B26B-6D71B3A06FD2} = {D4973E9C-9598-4511-8BBC-7F14936FFE10}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {6930CFF7-48FD-410E-A18D-6DE53E1F131D}
+ EndGlobalSection
+EndGlobal
diff --git a/RestaurantReviewsServerMsi/Product.wxs b/RestaurantReviewsServerMsi/Product.wxs
new file mode 100644
index 00000000..7162df64
--- /dev/null
+++ b/RestaurantReviewsServerMsi/Product.wxs
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ NOT (REMOVE ~= "ALL") OR WIX_UPGRADE_DETECTED
+ NOT (REMOVE ~= "ALL") OR WIX_UPGRADE_DETECTED
+
+
+
+
+
diff --git a/RestaurantReviewsServerMsi/RestaurantReviewsServerMsi.wixproj b/RestaurantReviewsServerMsi/RestaurantReviewsServerMsi.wixproj
new file mode 100644
index 00000000..b1861733
--- /dev/null
+++ b/RestaurantReviewsServerMsi/RestaurantReviewsServerMsi.wixproj
@@ -0,0 +1,90 @@
+
+
+
+ Debug
+ x86
+ 3.10
+ 44924695-c006-4d5a-880b-530ee78b1860
+ 2.0
+ RestaurantReviewsServerMsi
+ Package
+
+
+ bin\$(Configuration)\
+ obj\$(Configuration)\
+ Debug
+
+
+ bin\$(Configuration)\
+ obj\$(Configuration)\
+
+
+
+
+
+
+
+
+
+
+
+ ClientConsole
+ {cdb23559-bb93-4946-9802-9222a6586013}
+ True
+ True
+ Binaries;Content;Satellites
+ INSTALLFOLDER
+
+
+ InstallHelper
+ {68f6e2e8-e373-4d97-b26b-6d71b3a06fd2}
+ True
+ True
+ Binaries;Content;Satellites
+ INSTALLFOLDER
+
+
+ SoftWriters.RestaurantReviews.DataLibrary
+ {f2472982-dc7e-4eb8-86dd-45f1e3d189e8}
+ True
+ True
+ Binaries;Content;Satellites
+ INSTALLFOLDER
+
+
+ SoftWriters.RestaurantReviews.Service
+ {e7ff0558-c73d-4f0a-b56c-e5c595a87e41}
+ True
+ True
+ Binaries;Content;Satellites
+ INSTALLFOLDER
+
+
+ SoftWriters.RestaurantReviews.WebApi
+ {4f38eb50-bb3a-44e7-9fe2-3600fcc0be07}
+ True
+ True
+ Binaries;Content;Satellites
+ INSTALLFOLDER
+
+
+
+
+ $(WixExtDir)\WixUtilExtension.dll
+ WixUtilExtension
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/RestaurantReviewsServerMsi/config.wxi b/RestaurantReviewsServerMsi/config.wxi
new file mode 100644
index 00000000..08f559b1
--- /dev/null
+++ b/RestaurantReviewsServerMsi/config.wxi
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/RestaurantReviewsServerMsi/content.wxs b/RestaurantReviewsServerMsi/content.wxs
new file mode 100644
index 00000000..034f2007
--- /dev/null
+++ b/RestaurantReviewsServerMsi/content.wxs
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/RestaurantReviewsServerMsi/directoryStructure.wxs b/RestaurantReviewsServerMsi/directoryStructure.wxs
new file mode 100644
index 00000000..b966ca13
--- /dev/null
+++ b/RestaurantReviewsServerMsi/directoryStructure.wxs
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ServiceConsole/App.config b/ServiceConsole/App.config
new file mode 100644
index 00000000..7bcccbe8
--- /dev/null
+++ b/ServiceConsole/App.config
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ServiceConsole/Program.cs b/ServiceConsole/Program.cs
new file mode 100644
index 00000000..1556d7b1
--- /dev/null
+++ b/ServiceConsole/Program.cs
@@ -0,0 +1,21 @@
+using System;
+using System.ServiceModel.Web;
+using SoftWriters.RestaurantReviews.WebApi;
+
+namespace ServiceConsole
+{
+ // For testing and debugging
+
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var serviceHost = new WebServiceHost(typeof(ReviewApi));
+ serviceHost.Open();
+
+ Console.WriteLine("HTTP Service is running. Press any key to quit...");
+ Console.ReadKey();
+ serviceHost.Close();
+ }
+ }
+}
diff --git a/ServiceConsole/Properties/AssemblyInfo.cs b/ServiceConsole/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..e649c61d
--- /dev/null
+++ b/ServiceConsole/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ServiceConsole")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ServiceConsole")]
+[assembly: AssemblyCopyright("Copyright © 2021")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("c87a79fa-e477-4e8a-87a5-7dab90f6f50a")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/ServiceConsole/ServiceConsole.csproj b/ServiceConsole/ServiceConsole.csproj
new file mode 100644
index 00000000..f947275e
--- /dev/null
+++ b/ServiceConsole/ServiceConsole.csproj
@@ -0,0 +1,64 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {C87A79FA-E477-4E8A-87A5-7DAB90F6F50A}
+ Exe
+ ServiceConsole
+ ServiceConsole
+ v4.6.1
+ 512
+ true
+ true
+
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Designer
+
+
+
+
+ {4F38EB50-BB3A-44E7-9FE2-3600FCC0BE07}
+ SoftWriters.RestaurantReviews.WebApi
+
+
+
+
\ No newline at end of file
diff --git a/SoftWriters.RestaurantReviews.DataLibrary/DataObjects.cs b/SoftWriters.RestaurantReviews.DataLibrary/DataObjects.cs
new file mode 100644
index 00000000..a8d6fb6c
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.DataLibrary/DataObjects.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace SoftWriters.RestaurantReviews.DataLibrary
+{
+ [DataContract]
+ public class User
+ {
+ [DataMember] public Guid Id { get; set; }
+ [DataMember] public string Name { get; set; }
+ [DataMember] public Address Address { get; set; }
+
+ public User()
+ { }
+
+ public User(Guid id, string name, Address address)
+ {
+ Id = id;
+ Name = name;
+ Address = address;
+ }
+ }
+
+ [DataContract]
+ public class Address
+ {
+ [DataMember] public string StreetAddress { get; set; }
+ [DataMember] public string PostalCode { get; set; }
+ [DataMember] public string City { get; set; }
+ [DataMember] public string StateCode { get; set; }
+ [DataMember] public string Country { get; set; }
+
+ public Address()
+ { }
+
+ public Address(string street, string postalCode, string city, string stateCode, string country)
+ {
+ StreetAddress = street;
+ PostalCode = postalCode;
+ City = city;
+ StateCode = stateCode;
+ Country = country;
+ }
+ }
+
+ [DataContract]
+ public class Review
+ {
+ [DataMember] public Guid Id { get; set; }
+ [DataMember] public Guid UserId { get; set; }
+ [DataMember] public Guid RestaurantId { get; set; }
+ [DataMember] public int OverallRating { get; set; }
+ [DataMember] public int FoodRating { get; set; }
+ [DataMember] public int ServiceRating { get; set; }
+ [DataMember] public int CostRating { get; set; }
+ [DataMember] public string Comments { get; set; }
+
+ public Review()
+ { }
+
+ public Review(Guid id, Guid userId, Guid restaurantId, int overallRating, int foodRating, int serviceRating, int costRating, string comments)
+ {
+ Id = id;
+ UserId = userId;
+ RestaurantId = restaurantId;
+ OverallRating = overallRating;
+ FoodRating = foodRating;
+ ServiceRating = serviceRating;
+ CostRating = costRating;
+ Comments = comments;
+ }
+ }
+
+ [DataContract]
+ public class Restaurant
+ {
+ [DataMember] public Guid Id { get; set; }
+ [DataMember] public string Name { get; set; }
+ [DataMember] public Address Address { get; set; }
+
+ public Restaurant()
+ { }
+
+ public Restaurant(Guid id, string name, Address address)
+ {
+ Id = id;
+ Name = name;
+ Address = address;
+ }
+ }
+}
diff --git a/SoftWriters.RestaurantReviews.DataLibrary/Properties/AssemblyInfo.cs b/SoftWriters.RestaurantReviews.DataLibrary/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..e6198068
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.DataLibrary/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("SoftWriters.RestaurantReviews.DataLibrary")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SoftWriters.RestaurantReviews.DataLibrary")]
+[assembly: AssemblyCopyright("Copyright © 2021")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("f2472982-dc7e-4eb8-86dd-45f1e3d189e8")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/SoftWriters.RestaurantReviews.DataLibrary/SoftWriters.RestaurantReviews.DataLibrary.csproj b/SoftWriters.RestaurantReviews.DataLibrary/SoftWriters.RestaurantReviews.DataLibrary.csproj
new file mode 100644
index 00000000..b1aa571b
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.DataLibrary/SoftWriters.RestaurantReviews.DataLibrary.csproj
@@ -0,0 +1,51 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {F2472982-DC7E-4EB8-86DD-45F1E3D189E8}
+ Library
+ Properties
+ SoftWriters.RestaurantReviews.DataLibrary
+ SoftWriters.RestaurantReviews.DataLibrary
+ v4.6.1
+ 512
+ true
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SoftWriters.RestaurantReviews.DataLibrary/XmlDataStore.cs b/SoftWriters.RestaurantReviews.DataLibrary/XmlDataStore.cs
new file mode 100644
index 00000000..d01bf8f4
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.DataLibrary/XmlDataStore.cs
@@ -0,0 +1,120 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Xml;
+
+namespace SoftWriters.RestaurantReviews.DataLibrary
+{
+ public interface IDataStore
+ {
+ IEnumerable GetAllItems();
+ IEnumerable GetItems(Func predicate);
+ void AddItem(T item);
+ void DeleteItem(T item);
+ }
+
+ ///
+ /// Implements our data store as xml files in the local users documents folder.
+ /// It's not a great solution, but this would ultimately be stored in a database.
+ /// Just saving some time implementing a file based solution.
+ ///
+ ///
+ public class XmlDataStore : IDataStore
+ {
+ private static List SerializableTypes = new List
+ {
+ typeof(T),
+ typeof(Restaurant),
+ typeof(List),
+ typeof(User),
+ typeof(List),
+ typeof(Review),
+ typeof(List),
+ typeof(Address)
+ };
+
+ private readonly string _filename;
+ private readonly object _lock = new object();
+
+ public XmlDataStore()
+ {
+ string myDocsDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
+ _filename = Path.Combine(myDocsDir, string.Format("xmlDataStore_{0}.xml", typeof(T).Name));
+ EnsureDataExists();
+ }
+
+ public IEnumerable GetAllItems()
+ {
+ lock (_lock)
+ {
+ var items = Deserialize();
+ return items;
+ }
+ }
+
+ public IEnumerable GetItems(Func predicate)
+ {
+ IEnumerable items;
+ lock (_lock)
+ {
+ items = Deserialize();
+ }
+
+ return items.Where(predicate);
+ }
+
+ public void AddItem(T item)
+ {
+ lock (_lock)
+ {
+ var items = Deserialize().ToList();
+ items.Add(item);
+ Serialize(items);
+ }
+ }
+
+ public void DeleteItem(T item)
+ {
+ lock (_lock)
+ {
+ var items = Deserialize().ToList();
+ if(items.Contains(item))
+ items.Remove(item);
+ }
+ }
+
+ private IEnumerable Deserialize()
+ {
+ EnsureDataExists();
+
+ List items;
+ var serializer = new DataContractSerializer(typeof(T), SerializableTypes);
+ using (var reader = XmlReader.Create(_filename))
+ {
+ items = serializer.ReadObject(reader) as List;
+ }
+
+ return items;
+ }
+
+ private void EnsureDataExists()
+ {
+ if (File.Exists(_filename))
+ return;
+
+ Serialize(new List());
+ }
+
+ private void Serialize(IEnumerable items)
+ {
+ var serializer = new DataContractSerializer(typeof(T), SerializableTypes);
+ using (var writer = new XmlTextWriter(_filename, Encoding.UTF8))
+ {
+ serializer.WriteObject(writer, items);
+ }
+ }
+ }
+}
diff --git a/SoftWriters.RestaurantReviews.Service/App.config b/SoftWriters.RestaurantReviews.Service/App.config
new file mode 100644
index 00000000..1285d532
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.Service/App.config
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SoftWriters.RestaurantReviews.Service/Program.cs b/SoftWriters.RestaurantReviews.Service/Program.cs
new file mode 100644
index 00000000..1ba0870a
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.Service/Program.cs
@@ -0,0 +1,16 @@
+using System.ServiceProcess;
+
+namespace SoftWriters.RestaurantReviews.Service
+{
+ static class Program
+ {
+ static void Main()
+ {
+ var servicesToRun = new ServiceBase[]
+ {
+ new Service1()
+ };
+ ServiceBase.Run(servicesToRun);
+ }
+ }
+}
diff --git a/SoftWriters.RestaurantReviews.Service/Properties/AssemblyInfo.cs b/SoftWriters.RestaurantReviews.Service/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..cb82a2f7
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.Service/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("SoftWriters.RestaurantReviews.Service")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SoftWriters.RestaurantReviews.Service")]
+[assembly: AssemblyCopyright("Copyright © 2021")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("e7ff0558-c73d-4f0a-b56c-e5c595a87e41")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/SoftWriters.RestaurantReviews.Service/Service1.Designer.cs b/SoftWriters.RestaurantReviews.Service/Service1.Designer.cs
new file mode 100644
index 00000000..ab506de8
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.Service/Service1.Designer.cs
@@ -0,0 +1,37 @@
+namespace SoftWriters.RestaurantReviews.Service
+{
+ partial class Service1
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Component Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ components = new System.ComponentModel.Container();
+ this.ServiceName = "Service1";
+ }
+
+ #endregion
+ }
+}
diff --git a/SoftWriters.RestaurantReviews.Service/Service1.cs b/SoftWriters.RestaurantReviews.Service/Service1.cs
new file mode 100644
index 00000000..0ef1040a
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.Service/Service1.cs
@@ -0,0 +1,28 @@
+using System.ServiceModel;
+using System.ServiceModel.Web;
+using System.ServiceProcess;
+using SoftWriters.RestaurantReviews.WebApi;
+
+namespace SoftWriters.RestaurantReviews.Service
+{
+ public partial class Service1 : ServiceBase
+ {
+ private readonly ServiceHost _serviceHost;
+
+ public Service1()
+ {
+ InitializeComponent();
+ _serviceHost = new WebServiceHost(typeof(ReviewApi));
+ }
+
+ protected override void OnStart(string[] args)
+ {
+ _serviceHost.Open();
+ }
+
+ protected override void OnStop()
+ {
+ _serviceHost?.Close();
+ }
+ }
+}
diff --git a/SoftWriters.RestaurantReviews.Service/SoftWriters.RestaurantReviews.Service.csproj b/SoftWriters.RestaurantReviews.Service/SoftWriters.RestaurantReviews.Service.csproj
new file mode 100644
index 00000000..61bda151
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.Service/SoftWriters.RestaurantReviews.Service.csproj
@@ -0,0 +1,73 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {E7FF0558-C73D-4F0A-B56C-E5C595A87E41}
+ WinExe
+ SoftWriters.RestaurantReviews.Service
+ SoftWriters.RestaurantReviews.Service
+ v4.6.1
+ 512
+ true
+ true
+
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Component
+
+
+ Service1.cs
+
+
+
+
+
+
+ Designer
+
+
+
+
+ {4F38EB50-BB3A-44E7-9FE2-3600FCC0BE07}
+ SoftWriters.RestaurantReviews.WebApi
+
+
+
+
\ No newline at end of file
diff --git a/SoftWriters.RestaurantReviews.WebApi.Tests/GenerateData.cs b/SoftWriters.RestaurantReviews.WebApi.Tests/GenerateData.cs
new file mode 100644
index 00000000..6844b113
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.WebApi.Tests/GenerateData.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using SoftWriters.RestaurantReviews.DataLibrary;
+
+namespace SoftWriters.RestaurantReviews.WebApi.Tests
+{
+ // Some unit test methods to generate local data for testing purposes
+
+ [TestClass]
+ public class GenerateData
+ {
+ // Uncomment [TestMethod] attributes on the given methods in order to run
+ // unit tests which clear and load database with test data
+
+ private List _restaurants;
+ private List _reviews;
+ private List _users;
+
+ [TestMethod]
+ public void GenerateAllData()
+ {
+ GenerateRestaurantData();
+ GenerateUserData();
+ GenerateReviewData();
+ }
+
+ //[TestMethod]
+ public void GenerateRestaurantData()
+ {
+ string myDocsDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
+ var filename = Path.Combine(myDocsDir, string.Format("xmlDataStore_{0}.xml", nameof(Restaurant)));
+ if(File.Exists(filename))
+ File.Delete(filename);
+
+ _restaurants = new List()
+ {
+ new Restaurant(Guid.NewGuid(), "Grille 565", new Address("565 Lincoln Ave", "15202", "Pittsburgh", "PA", "United States")),
+ new Restaurant(Guid.NewGuid(), "202 Hometown Tacos", new Address("202 Lincoln Ave", "15202", "Pittsburgh", "PA", "United States")),
+ new Restaurant(Guid.NewGuid(), "Bryan's Speakeasy", new Address("205 N Sprague Ave", "15202", "Pittsburgh", "PA", "United States")),
+ new Restaurant(Guid.NewGuid(), "Katz's Delicatessen", new Address("205 E Houston St", "10002", "New York", "NY", "United States"))
+ };
+
+ var dataStore = new XmlDataStore();
+ foreach(var item in _restaurants)
+ dataStore.AddItem(item);
+ }
+
+ //[TestMethod]
+ public void GenerateUserData()
+ {
+ string myDocsDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
+ var filename = Path.Combine(myDocsDir, string.Format("xmlDataStore_{0}.xml", nameof(User)));
+ if (File.Exists(filename))
+ File.Delete(filename);
+
+ _users = new List()
+ {
+ new User(Guid.NewGuid(), "John Doe", new Address("703 Highland", "16335", "Meadville", "PA", "United States")),
+ new User(Guid.NewGuid(), "Jane Doe", new Address("12 Marie Ave", "15202", "Pittsburgh", "PA", "United States"))
+ };
+
+ var dataStore = new XmlDataStore();
+ foreach (var item in _users)
+ dataStore.AddItem(item);
+ }
+
+ //[TestMethod]
+ public void GenerateReviewData()
+ {
+ string myDocsDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
+ var filename = Path.Combine(myDocsDir, string.Format("xmlDataStore_{0}.xml", nameof(Review)));
+ if (File.Exists(filename))
+ File.Delete(filename);
+
+ _reviews = new List()
+ {
+ new Review(Guid.NewGuid(), _users[0].Id, _restaurants[0].Id, 4, 4, 4, 3, "Cozy!"),
+ new Review(Guid.NewGuid(), _users[1].Id, _restaurants[1].Id, 3, 3, 2, 3, "Meh!")
+ };
+
+ var dataStore = new XmlDataStore();
+ foreach (var item in _reviews)
+ dataStore.AddItem(item);
+ }
+ }
+}
diff --git a/SoftWriters.RestaurantReviews.WebApi.Tests/Properties/AssemblyInfo.cs b/SoftWriters.RestaurantReviews.WebApi.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..46e1a064
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.WebApi.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,20 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("SoftWriters.RestaurantReviews.WebApi.Tests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SoftWriters.RestaurantReviews.WebApi.Tests")]
+[assembly: AssemblyCopyright("Copyright © 2021")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+[assembly: ComVisible(false)]
+
+[assembly: Guid("b36a5c8d-5c18-47e9-a3d9-070fd70706ad")]
+
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/SoftWriters.RestaurantReviews.WebApi.Tests/ReviewApiTests.cs b/SoftWriters.RestaurantReviews.WebApi.Tests/ReviewApiTests.cs
new file mode 100644
index 00000000..cf301556
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.WebApi.Tests/ReviewApiTests.cs
@@ -0,0 +1,131 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using SoftWriters.RestaurantReviews.DataLibrary;
+
+namespace SoftWriters.RestaurantReviews.WebApi.Tests
+{
+ [TestClass]
+ public class ReviewApiTests
+ {
+ private TestDataStore _restaurantStore;
+ private TestDataStore _reviewDataStore;
+ private TestDataStore _userDataStore;
+ private ReviewApi _reviewApi;
+
+ [TestInitialize]
+ public void Initialize()
+ {
+ var restaurants = new List()
+ {
+ new Restaurant(Guid.NewGuid(), "Grille 565",
+ new Address("565 Lincoln Ave", "15202", "Pittsburgh", "PA", "United States")),
+ new Restaurant(Guid.NewGuid(), "202 Hometown Tacos",
+ new Address("202 Lincoln Ave", "15202", "Pittsburgh", "PA", "United States")),
+ new Restaurant(Guid.NewGuid(), "Bryan's Speakeasy",
+ new Address("205 N Sprague Ave", "15202", "Pittsburgh", "PA", "United States")),
+ new Restaurant(Guid.NewGuid(), "Katz's Delicatessen",
+ new Address("205 E Houston St", "10002", "New York", "NY", "United States"))
+ };
+
+ var users = new List()
+ {
+ new User(Guid.NewGuid(), "John Doe",
+ new Address("123 Main", "15202", "Pittsburgh", "PA", "United States")),
+ new User(Guid.NewGuid(), "Jane Doe",
+ new Address("123 Main", "15202", "Pittsburgh", "PA", "United States"))
+ };
+
+ var reviews = new List()
+ {
+ new Review(Guid.NewGuid(), users[0].Id, restaurants[0].Id, 4, 4, 4, 3, "Cozy!"),
+ new Review(Guid.NewGuid(), users[1].Id, restaurants[1].Id, 3, 3, 3, 3, "meh")
+ };
+
+ _restaurantStore = new TestDataStore(restaurants);
+ _reviewDataStore = new TestDataStore(reviews);
+ _userDataStore = new TestDataStore(users);
+ _reviewApi = new ReviewApi(_restaurantStore, _reviewDataStore, _userDataStore);
+ }
+
+ [TestMethod]
+ public void AddRestaurant_ReturnsTrue_WhenRestaurantWithMatchingNameAndAddressDoesNotExist()
+ {
+ bool result = _reviewApi.AddRestaurant("Hanks", "123 Main", "City", "PA", "00000", "US");
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ public void AddRestaurant_ReturnsFalse_WhenRestaurantWithMatchingNameAndAddressExists()
+ {
+ bool result = _reviewApi.AddRestaurant("Bryan's Speakeasy", "205 N Sprague Ave", "Pittsburgh", "PA", "15202", "United States");
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ public void GetRestaurants_ReturnsOnlyRestaurantsMatchingAllCriteria()
+ {
+ var restaurants = _reviewApi.GetRestaurants("", "Pittsburgh", "", "", "United States").ToList();
+ Assert.AreEqual(3, restaurants.Count);
+ Assert.IsTrue(restaurants.All(item => item.Address.City == "Pittsburgh"));
+ }
+
+ [TestMethod]
+ public void AddReview_ReturnsFalse_WhenUserDoesNotExist()
+ {
+ var restaurant = _restaurantStore.GetAllItems().First();
+ bool result = _reviewApi.AddReview(Guid.NewGuid(), restaurant.Id, 5, 5, 5, 5, "");
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ public void AddReview_ReturnsFalse_WhenReviewExistsForUser()
+ {
+ var review = _reviewDataStore.GetAllItems().First();
+ bool result = _reviewApi.AddReview(review.UserId, review.RestaurantId, 0, 0, 0, 0, "");
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ public void AddReview_ReturnsTrue_WhenUsersExistsAndReviewDoesNot()
+ {
+ var user = _userDataStore.GetAllItems().First();
+ var restaurant = _restaurantStore.GetAllItems().Last();
+ bool result = _reviewApi.AddReview(user.Id, restaurant.Id, 3, 3, 3, 5, "Too expensive!");
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ public void GetReviews_ReturnsOnlyReviewsMatchingCriteria()
+ {
+ var user = _userDataStore.GetAllItems().First();
+ var reviews = _reviewApi.GetReviews(user.Id, Guid.Empty).ToList();
+ Assert.AreEqual(1, reviews.Count);
+
+ var restaurant = _restaurantStore.GetAllItems().ToList()[1];
+ reviews = _reviewApi.GetReviews(Guid.Empty, restaurant.Id).ToList();
+ Assert.AreEqual(1, reviews.Count);
+
+ }
+
+ [TestMethod]
+ public void GetReviews_ReturnsAllReviews_WhenNoInputIsGiven()
+ {
+ var reviews = _reviewApi.GetReviews(Guid.Empty, Guid.Empty).ToList();
+ Assert.AreEqual(2, reviews.Count);
+ }
+
+ [TestMethod]
+ public void DeleteReview_ReturnsTrueAndRemovesItem()
+ {
+ var reviews = _reviewApi.GetReviews(Guid.Empty, Guid.Empty).ToList();
+ Assert.AreEqual(2, reviews.Count);
+
+ _reviewApi.DeleteReview(reviews[0].Id);
+
+ reviews = _reviewApi.GetReviews(Guid.Empty, Guid.Empty).ToList();
+ Assert.AreEqual(1, reviews.Count);
+ }
+ }
+}
diff --git a/SoftWriters.RestaurantReviews.WebApi.Tests/SoftWriters.RestaurantReviews.WebApi.Tests.csproj b/SoftWriters.RestaurantReviews.WebApi.Tests/SoftWriters.RestaurantReviews.WebApi.Tests.csproj
new file mode 100644
index 00000000..47856941
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.WebApi.Tests/SoftWriters.RestaurantReviews.WebApi.Tests.csproj
@@ -0,0 +1,80 @@
+
+
+
+
+
+ Debug
+ AnyCPU
+ {B36A5C8D-5C18-47E9-A3D9-070FD70706AD}
+ Library
+ Properties
+ SoftWriters.RestaurantReviews.WebApi.Tests
+ SoftWriters.RestaurantReviews.WebApi.Tests
+ v4.6.1
+ 512
+ {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 15.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
+ False
+ UnitTest
+
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll
+
+
+ ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {F2472982-DC7E-4EB8-86DD-45F1E3D189E8}
+ SoftWriters.RestaurantReviews.DataLibrary
+
+
+ {4F38EB50-BB3A-44E7-9FE2-3600FCC0BE07}
+ SoftWriters.RestaurantReviews.WebApi
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SoftWriters.RestaurantReviews.WebApi.Tests/TestDataStore.cs b/SoftWriters.RestaurantReviews.WebApi.Tests/TestDataStore.cs
new file mode 100644
index 00000000..383d74a3
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.WebApi.Tests/TestDataStore.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using SoftWriters.RestaurantReviews.DataLibrary;
+
+namespace SoftWriters.RestaurantReviews.WebApi.Tests
+{
+ public class TestDataStore : IDataStore
+ {
+ private readonly List _items;
+
+ public TestDataStore(IEnumerable initialItems)
+ {
+ _items = initialItems.ToList();
+ }
+
+ public IEnumerable GetAllItems()
+ {
+ return _items;
+ }
+
+ public IEnumerable GetItems(Func predicate)
+ {
+ return _items.Where(predicate);
+ }
+
+ public void AddItem(T item)
+ {
+ _items.Add(item);
+ }
+
+ public void DeleteItem(T item)
+ {
+ _items.Remove(item);
+ }
+ }
+}
diff --git a/SoftWriters.RestaurantReviews.WebApi.Tests/packages.config b/SoftWriters.RestaurantReviews.WebApi.Tests/packages.config
new file mode 100644
index 00000000..238840db
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.WebApi.Tests/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/SoftWriters.RestaurantReviews.WebApi/Api.cs b/SoftWriters.RestaurantReviews.WebApi/Api.cs
new file mode 100644
index 00000000..ed33be81
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.WebApi/Api.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.ServiceModel;
+using System.ServiceModel.Syndication;
+using System.ServiceModel.Web;
+using SoftWriters.RestaurantReviews.DataLibrary;
+
+namespace SoftWriters.RestaurantReviews.WebApi
+{
+ public class ReviewApi : IReviewApi
+ {
+ private readonly IDataStore _restaurantDataStore;
+ private readonly IDataStore _reviewDataStore;
+ private readonly IDataStore _userDataStore;
+
+ public ReviewApi()
+ {
+ _restaurantDataStore = new XmlDataStore();
+ _reviewDataStore = new XmlDataStore();
+ _userDataStore = new XmlDataStore();
+ }
+
+ public ReviewApi(IDataStore restaurantDataStore, IDataStore reviewDataStore, IDataStore userDataStore)
+ {
+ _restaurantDataStore = restaurantDataStore;
+ _reviewDataStore = reviewDataStore;
+ _userDataStore = userDataStore;
+
+ }
+
+ public IEnumerable GetRestaurants(string street, string city, string stateCode, string postalCode, string country)
+ {
+ var matches = _restaurantDataStore.GetItems(item =>
+ (string.IsNullOrEmpty(street) || item.Address.StreetAddress == street) &&
+ (string.IsNullOrEmpty(city) || item.Address.City == city) &&
+ (string.IsNullOrEmpty(stateCode) || item.Address.StateCode == stateCode) &&
+ (string.IsNullOrEmpty(postalCode) || item.Address.PostalCode == postalCode) &&
+ (string.IsNullOrEmpty(country) || item.Address.Country == country));
+
+ return matches;
+ }
+
+ public IEnumerable GetReviews(Guid userId, Guid restaurantId)
+ {
+ var matches = _reviewDataStore.GetItems(item =>
+ (userId == Guid.Empty || item.UserId == userId) &&
+ (restaurantId == Guid.Empty || item.RestaurantId == restaurantId));
+
+ return matches;
+ }
+
+ public bool AddRestaurant(string name, string street, string city, string stateCode, string postalCode, string country)
+ {
+ var matches = _restaurantDataStore.GetItems(item => item.Name == name
+ && item.Address.StreetAddress == street
+ && item.Address.City == city
+ && item.Address.StateCode == stateCode
+ && item.Address.PostalCode == postalCode
+ && item.Address.Country == country);
+
+ if (matches.Any())
+ return false;
+
+ var address = new Address(street, postalCode, city, stateCode, country);
+ var id = Guid.NewGuid();
+ var restaurant = new Restaurant(id, name, address);
+ _restaurantDataStore.AddItem(restaurant);
+
+ return true;
+ }
+
+ public bool AddReview(Guid userId, Guid restaurantId, int overallRating, int foodRating, int serviceRating,
+ int costRating, string comments)
+ {
+ var matchingReviews = _reviewDataStore.GetItems(item => item.UserId == userId && item.RestaurantId == restaurantId);
+ if (matchingReviews.Any())
+ return false;
+
+ var matchingUser = _userDataStore.GetItems(item => item.Id == userId).SingleOrDefault();
+ if (matchingUser == null)
+ return false;
+
+ var id = Guid.NewGuid();
+ var review = new Review(id, userId, restaurantId, overallRating, foodRating, serviceRating, costRating, comments);
+ _reviewDataStore.AddItem(review);
+ return true;
+ }
+
+ public bool DeleteReview(Guid id)
+ {
+ var review = _reviewDataStore.GetItems(item => item.Id == id).SingleOrDefault();
+ if(review != null)
+ _reviewDataStore.DeleteItem(review);
+
+ return true;
+ }
+ }
+
+ [ServiceContract]
+ public interface IReviewApi
+ {
+ [WebInvoke(UriTemplate = "/GetRestaurants?street={street}&city={city}&stateCode={stateCode}&postalCode={postalCode}&country={country}", Method = "GET", ResponseFormat = WebMessageFormat.Json)]
+ [OperationContract]
+ IEnumerable GetRestaurants(string street, string city, string stateCode, string postalCode, string country);
+
+ [WebInvoke(UriTemplate = "/GetReviews?userId={userId}&restaurantId={restaurantId}", Method = "GET", ResponseFormat = WebMessageFormat.Json)]
+ [OperationContract]
+ IEnumerable GetReviews(Guid userId, Guid restaurantId);
+
+ [WebInvoke(UriTemplate = "/AddRestaurant?name={name}&street={street}&city={city}&stateCode={stateCode}&postalCode={postalCode}&country={country}", Method = "POST", ResponseFormat = WebMessageFormat.Json)]
+ [OperationContract]
+ bool AddRestaurant(string name, string street, string city, string stateCode, string postalCode, string country);
+
+ [WebInvoke(UriTemplate = "/AddReview?userId={userId}&restaurantId={restaurantId}&overallRating={overallRating}&foodRating={foodRating}&serviceRating={serviceRating}&costRating={costRating}&comments={comments}", Method = "POST", ResponseFormat = WebMessageFormat.Json)]
+ [OperationContract]
+ bool AddReview(Guid userId, Guid restaurantId, int overallRating, int foodRating, int serviceRating, int costRating, string comments);
+
+ [WebInvoke(UriTemplate = "/DeleteReview?id={id}", Method = "DELETE", ResponseFormat = WebMessageFormat.Json)]
+ [OperationContract]
+ bool DeleteReview(Guid id);
+ }
+}
diff --git a/SoftWriters.RestaurantReviews.WebApi/Properties/AssemblyInfo.cs b/SoftWriters.RestaurantReviews.WebApi/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..cd5a59de
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.WebApi/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("SoftWriters.RestaurantReviews.WebApi")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SoftWriters.RestaurantReviews.WebApi")]
+[assembly: AssemblyCopyright("Copyright © 2021")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("4f38eb50-bb3a-44e7-9fe2-3600fcc0be07")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/SoftWriters.RestaurantReviews.WebApi/SoftWriterException.cs b/SoftWriters.RestaurantReviews.WebApi/SoftWriterException.cs
new file mode 100644
index 00000000..4b147988
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.WebApi/SoftWriterException.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace SoftWriters.RestaurantReviews.WebApi
+{
+ public class SoftWriterException : Exception
+ {
+ public SoftWriterException()
+ { }
+
+ public SoftWriterException(string message) : base(message)
+ { }
+
+ public SoftWriterException(string message, Exception innerException) : base(message, innerException)
+ { }
+ }
+}
diff --git a/SoftWriters.RestaurantReviews.WebApi/SoftWriters.RestaurantReviews.WebApi.csproj b/SoftWriters.RestaurantReviews.WebApi/SoftWriters.RestaurantReviews.WebApi.csproj
new file mode 100644
index 00000000..b2a37b03
--- /dev/null
+++ b/SoftWriters.RestaurantReviews.WebApi/SoftWriters.RestaurantReviews.WebApi.csproj
@@ -0,0 +1,58 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {4F38EB50-BB3A-44E7-9FE2-3600FCC0BE07}
+ Library
+ Properties
+ SoftWriters.RestaurantReviews.WebApi
+ SoftWriters.RestaurantReviews.WebApi
+ v4.6.1
+ 512
+ true
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {F2472982-DC7E-4EB8-86DD-45F1E3D189E8}
+ SoftWriters.RestaurantReviews.DataLibrary
+
+
+
+
\ No newline at end of file