├── .gitignore ├── Async ├── Async101.workbook ├── AsyncWPF.workbook ├── README.md └── Screenshots │ └── async-inspector.png ├── Azure ├── EasyTables101.workbook ├── EasyTablesImages │ ├── portal.png │ ├── records.png │ ├── schema.png │ ├── settings.png │ └── setup1.png ├── EasyTablesWPF.workbook ├── MobileService101.workbook ├── Screenshots │ ├── azure-configuration.png │ └── azure-filter.png ├── TryAzure-Mac.workbook ├── TryAzure-WPF.workbook └── TryAzure-iOS.workbook ├── Collections ├── Arrays.workbook ├── README.md └── Screenshots │ └── arrays-inspector.png ├── Csharp6 ├── Arrays.workbook ├── OperatorOverloading.workbook ├── README.md ├── Screenshots │ └── csharp6-inspector.png ├── Structs.workbook └── csharp6.workbook ├── LICENSE ├── MagicEightBall ├── README.md ├── Screenshots │ └── 8ball-inspector.png └── magic8ball.workbook ├── Mastermind ├── README.md ├── Screenshots │ ├── mastermind-inspector.png │ └── mastermind-play.png └── mastermind.workbook ├── MobileCenter ├── README.md ├── Screenshots │ └── sonoma.png ├── Sonoma-httpclient.workbook ├── Sonoma-webclient.workbook └── Sonoma-webrequest.workbook ├── Nuget ├── JsonNET1.workbook └── Screenshots │ ├── JsonNET1.png │ ├── add-newtonsoft-sml.png │ ├── add-newtonsoft.png │ ├── add-package-sml.png │ └── add-package.png ├── README.md ├── Screenshots ├── 8ball-inspector-sml.png ├── csharp6-inspector-sml.png └── mastermind-inspector-sml.png ├── ScrollingLists ├── actionsource.png ├── customsource.png ├── detailsource.png ├── editsource.png ├── imagesource.png ├── ios-tableview1.workbook ├── mysource.png ├── selectionsource.png └── workicon.png ├── Strava ├── ConnectWithStrava.png ├── ConnectWithStrava@2x.png ├── LogInWithStrava.png ├── LogInWithStrava@2x.png ├── README.md ├── Screenshots │ ├── list-activities-wpf.png │ ├── list-activities.png │ └── single-activity.png ├── StravaApi-mac.workbook ├── StravaApi-wpf.workbook ├── strava-api-sml.png └── strava-api.png ├── Visualizers ├── README.md ├── Screenshots │ ├── ios-mac-sml.png │ └── ios-mac.png ├── Visualizers-Console.workbook ├── Visualizers-Mac.workbook ├── Visualizers-WPF.workbook ├── Visualizers-iOS.workbook └── bridge.jpg ├── Wpf └── WpfPermissionTest.workbook └── Xamarin.Forms ├── ListView1-android.workbook ├── ListView1-ios.workbook ├── LoadXaml.workbook ├── Screenshots ├── ListView1.png └── WorkbooksFormsTest.png └── WorkbookFormsTest.workbook /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | *.sap 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | # NuGet v3's project.json files produces more ignoreable files 154 | *.nuget.props 155 | *.nuget.targets 156 | 157 | # Microsoft Azure Build Output 158 | csx/ 159 | *.build.csdef 160 | 161 | # Microsoft Azure Emulator 162 | ecf/ 163 | rcf/ 164 | 165 | # Microsoft Azure ApplicationInsights config file 166 | ApplicationInsights.config 167 | 168 | # Windows Store app package directory 169 | AppPackages/ 170 | BundleArtifacts/ 171 | 172 | # Visual Studio cache files 173 | # files ending in .cache can be ignored 174 | *.[Cc]ache 175 | # but keep track of directories ending in .cache 176 | !*.[Cc]ache/ 177 | 178 | # Others 179 | ClientBin/ 180 | ~$* 181 | *~ 182 | *.dbmdl 183 | *.dbproj.schemaview 184 | *.pfx 185 | *.publishsettings 186 | node_modules/ 187 | orleans.codegen.cs 188 | 189 | # RIA/Silverlight projects 190 | Generated_Code/ 191 | 192 | # Backup & report files from converting an old project file 193 | # to a newer Visual Studio version. Backup files are not needed, 194 | # because we have git ;-) 195 | _UpgradeReport_Files/ 196 | Backup*/ 197 | UpgradeLog*.XML 198 | UpgradeLog*.htm 199 | 200 | # SQL Server files 201 | *.mdf 202 | *.ldf 203 | 204 | # Business Intelligence projects 205 | *.rdl.data 206 | *.bim.layout 207 | *.bim_*.settings 208 | 209 | # Microsoft Fakes 210 | FakesAssemblies/ 211 | 212 | # GhostDoc plugin setting file 213 | *.GhostDoc.xml 214 | 215 | # Node.js Tools for Visual Studio 216 | .ntvs_analysis.dat 217 | 218 | # Visual Studio 6 build log 219 | *.plg 220 | 221 | # Visual Studio 6 workspace options file 222 | *.opt 223 | 224 | # Visual Studio LightSwitch build output 225 | **/*.HTMLClient/GeneratedArtifacts 226 | **/*.DesktopClient/GeneratedArtifacts 227 | **/*.DesktopClient/ModelManifest.xml 228 | **/*.Server/GeneratedArtifacts 229 | **/*.Server/ModelManifest.xml 230 | _Pvt_Extensions 231 | 232 | # Paket dependency manager 233 | .paket/paket.exe 234 | 235 | # FAKE - F# Make 236 | .fake/ 237 | -------------------------------------------------------------------------------- /Async/Async101.workbook: -------------------------------------------------------------------------------- 1 | ```json 2 | {"platform":"MacNet45","uti":"com.xamarin.Workbook"} 3 | ``` 4 | 5 | # Async 101 6 | 7 | A quick explanation of why `async-await` code is easier to read/follow 8 | than old-style callbacks. Read more at 9 | [Xamarin's async support overview](https://developer.xamarin.com/guides/cross-platform/advanced/async_support_overview/). 10 | 11 | 12 | ## The Old Way - Callbacks 13 | 14 | 1. Get all the config out of the way 15 | 16 | ```csharp 17 | var webClient = new System.Net.WebClient(); 18 | webClient.Encoding = System.Text.Encoding.UTF8; 19 | 20 | var xmlUri = new Uri("http://api.geonames.org/earthquakesXML?north=44.1&south=-9.9&east=-22.4&west=55.2&username=bertt"); 21 | ``` 22 | 23 | 2. We have to set up the 'callback' handler which is called 24 | when the network operation completes 25 | 26 | ```csharp 27 | var response1 = ""; 28 | webClient.DownloadStringCompleted += (s, e) => { 29 | response1 = e.Result; // get the downloaded text 30 | }; 31 | webClient.DownloadStringAsync(xmlUri); 32 | ``` 33 | 34 | 3. *Then* we call the actual method - confusing huh? The code we just wrote 35 | above gets executed *after* this completes! 36 | 37 | ```csharp 38 | // this will be blank because the completed even won't have fired in most cases 39 | // type `response1` in the interactive window to confirm that the XML was downloaded 40 | response1 41 | ``` 42 | 43 | (we'll check on the response later) 44 | 45 | ## The New Way - Async 46 | 47 | 1. Get all the config out of the way 48 | 49 | ```csharp 50 | #r "System.Net.Http" 51 | ``` 52 | 53 | ```csharp 54 | using System.Net.Http; 55 | using System.Threading.Tasks; 56 | using System.IO; 57 | 58 | var jsonUri = "http://api.geonames.org/earthquakesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&username=bertt"; 59 | ``` 60 | 61 | 2. The network call code is in a logical place 62 | 63 | ```csharp 64 | var response2 = await new HttpClient().GetStringAsync(jsonUri); 65 | ``` 66 | 67 | 3. *Then* we do something with the result - much easier to follow 68 | what's happening in the code! 69 | 70 | ```csharp 71 | Console.WriteLine(response2.Substring(0,100) + "..."); 72 | ``` 73 | 74 | ## Finally... 75 | 76 | Check `response1` from the first example again - the completed callback 77 | has probably been executed by now... 78 | 79 | ```csharp 80 | response1 81 | ``` 82 | -------------------------------------------------------------------------------- /Async/AsyncWPF.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platform: WPF 4 | packages: [] 5 | --- 6 | 7 | 8 | # Async 101 9 | 10 | A quick explanation of why `async-await` code is easier to read/follow 11 | than old-style callbacks. Read more at 12 | [Xamarin's async support overview](https://developer.xamarin.com/guides/cross-platform/advanced/async_support_overview/). 13 | 14 | 15 | ## The Old Way - Callbacks 16 | 17 | 1. Get all the config out of the way 18 | 19 | ```csharp 20 | var webClient = new System.Net.WebClient(); 21 | webClient.Encoding = System.Text.Encoding.UTF8; 22 | 23 | var xmlUri = new Uri("http://api.geonames.org/earthquakesXML?north=44.1&south=-9.9&east=-22.4&west=55.2&username=bertt"); 24 | ``` 25 | 26 | 2. We have to set up the 'callback' handler which is called 27 | when the network operation completes 28 | 29 | ```csharp 30 | var response1 = ""; 31 | webClient.DownloadStringCompleted += (s, e) => { 32 | response1 = e.Result; // get the downloaded text 33 | }; 34 | webClient.DownloadStringAsync(xmlUri); 35 | ``` 36 | 37 | 3. *Then* we call the actual method - confusing huh? The code we just wrote 38 | above gets executed *after* this completes! 39 | 40 | ```csharp 41 | // this will be blank because the completed even won't have fired in most cases 42 | // type `response1` in the interactive window to confirm that the XML was downloaded 43 | response1 44 | ``` 45 | 46 | (we'll check on the response later) 47 | 48 | ## The New Way - Async 49 | 50 | 1. Get all the config out of the way 51 | 52 | ```csharp 53 | #r "System.Net.Http" 54 | ``` 55 | 56 | ```csharp 57 | using System.Net.Http; 58 | using System.Threading.Tasks; 59 | using System.IO; 60 | 61 | var jsonUri = "http://api.geonames.org/earthquakesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&username=bertt"; 62 | ``` 63 | 64 | 2. The network call code is in a logical place 65 | 66 | ```csharp 67 | var response2 = await new HttpClient().GetStringAsync(jsonUri); 68 | ``` 69 | 70 | 3. *Then* we do something with the result - much easier to follow 71 | what's happening in the code! 72 | 73 | ```csharp 74 | Console.WriteLine(response2.Substring(0,100) + "..."); 75 | ``` 76 | 77 | ## Finally... 78 | 79 | Check `response1` from the first example again - the completed callback 80 | has probably been executed by now... 81 | 82 | ```csharp 83 | response1 84 | ``` 85 | -------------------------------------------------------------------------------- /Async/README.md: -------------------------------------------------------------------------------- 1 | Async-Await (Inspector) 2 | ========= 3 | 4 | A simple `callback` versus `await` demo related to 5 | [Xamarin's async support overview](https://developer.xamarin.com/guides/cross-platform/advanced/async_support_overview/). 6 | 7 | The callback scenario is a little hard to explain in the interactive environment, by its nature... 8 | 9 | ![](Screenshots/async-inspector.png) 10 | -------------------------------------------------------------------------------- /Async/Screenshots/async-inspector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Async/Screenshots/async-inspector.png -------------------------------------------------------------------------------- /Azure/EasyTables101.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platform: MacNet45 4 | packages: [] 5 | --- 6 | 7 | 8 | # Playing around with Azure EasyTables 9 | 10 | This workbook shows how to access data stored in an **Azure Easy Table** (one of the new features of https://portal.azure.com) using simple REST principles. `WebClient` and some REST URLs is all that's required! The workbook will cover: 11 | 12 | * Configuration 13 | 14 | * List data 15 | 16 | * Filter data 17 | 18 | * Insert data 19 | 20 | * Update data 21 | 22 | * Delete data 23 | 24 | ## Configuration 25 | 26 | Sign-up for Azure and create an **App Service** that contains an **Easy Table**. This will include creating a **Database** and then an **Easy Table** with a **Schema**. The settings used in this workbook are: 27 | 28 | **App Service:** xamarin-todo-cd 29 | **Database:** xamarin-todo-cd_db (this is not visible to the consuming API) 30 | **Table Name:** todo 31 | **Schema columns:** text (string), complete (bool) 32 | 33 | ### Schema Config 34 | 35 | ![](EasyTablesImages/schema.png) 36 | 37 | ### Settings 38 | 39 | Start with the URL for the Azure App Service available in the Settings blade: 40 | 41 | ![](EasyTablesImages/settings.png) 42 | 43 | ```csharp 44 | static string subdomain = "xamarin-todo-cd"; // your subdomain 45 | ``` 46 | 47 | The REST API is similar to the old-style Azure Mobile Service [described on MSDN](https://msdn.microsoft.com/en-us/library/azure/jj677199.aspx) - use the name of the **Easy Table** in the URL (eg. `todo` in this case): 48 | 49 | ```csharp 50 | static string GetAllUrl = "https://" + subdomain + ".azurewebsites.net/tables/todo"; 51 | static string GetOneUrl = "https://" + subdomain + ".azurewebsites.net/tables/todo?$filter=(id%20eq%20%27{0}%27)"; 52 | static string AddUrl = "https://" + subdomain + ".azurewebsites.net/tables/todo"; 53 | static string UpdateUrl = "https://" + subdomain + ".azurewebsites.net/tables/todo/{0}"; 54 | static string DeleteUrl = "https://" + subdomain + ".azurewebsites.net/tables/todo/{0}"; 55 | // yes, some of these are the same, the HTTP method [GET, POST, DELETE] differentiates them in the code 56 | ``` 57 | 58 | Finally, this helper method creates a correctly configured `WebClient` that includes the headers required to access the Azure Easy Table: 59 | 60 | ```csharp 61 | using System.Net; 62 | WebClient AzureClient () { 63 | WebClient client = new WebClient(); 64 | client.Headers.Add (HttpRequestHeader.Accept, "application/json"); 65 | client.Headers.Add (HttpRequestHeader.ContentType, "application/json"); 66 | client.Headers.Add ("ZUMO-API-VERSION", "2.0.0"); // for working with Easy Table 67 | return client; 68 | } 69 | ``` 70 | 71 | The examples below assume you've manually created a **todo** Easy Table in your Azure service, and added `text` and `complete` columns to the schema. 72 | 73 | ## List Data 74 | 75 | Using the WebClient we can manually set HTTP headers and access data stored in Azure. 76 | 77 | ```csharp 78 | var tasks = new List(); 79 | var responseString = ""; 80 | try 81 | { 82 | WebClient client = AzureClient(); 83 | var response = client.DownloadData (GetAllUrl); // GET 84 | // ...and wait... 85 | responseString = System.Text.Encoding.UTF8.GetString(response); 86 | } 87 | catch (System.Net.WebException e) 88 | { 89 | Console.WriteLine ("X-ZUMO-APPLICATION failed " + e.Message); 90 | } 91 | ``` 92 | 93 | The responseString contains the data returned from Azure in a JSON-formatted string: 94 | 95 | ```csharp 96 | responseString 97 | ``` 98 | 99 | The data can also be viewed in the **Easy Tables** blade of the Azure portal: 100 | 101 | ![](EasyTablesImages/records.png) 102 | 103 | ## Filter the list 104 | 105 | The URL scheme also supports filtering, so to restrict the list to *just completed tasks*, add a filter and send the request again: 106 | 107 | ```csharp 108 | var filterUrl = GetAllUrl + "?$filter=(complete%20eq%20true)"; // YOU COULD ALSO CHANGE THE FILTER 109 | responseString = ""; 110 | try 111 | { 112 | WebClient client = AzureClient(); 113 | var response = client.DownloadData (filterUrl); // GET 114 | responseString = System.Text.Encoding.UTF8.GetString(response); 115 | } 116 | catch (System.Net.WebException e) 117 | { 118 | Console.WriteLine("X-ZUMO-APPLICATION failed:" + e.Message); 119 | } 120 | ``` 121 | 122 | Now if we check the response, it is a shorter list! 123 | 124 | ```csharp 125 | responseString 126 | ``` 127 | 128 | ## Get a single item 129 | 130 | Individual items can be retrieved using a different REST URL. Copy a GUID from the previous response into the `id` variable. This `id` is added to the filter in the URL: 131 | 132 | ```csharp 133 | var id = @"C066834E-4CA2-4804-B746-6DCD14B45CC5"; // UPDATE THIS WITH AN ID FROM ABOVE 134 | var getUrl = String.Format(GetOneUrl, id); 135 | getUrl 136 | ``` 137 | 138 | Now use this modified URL (including the filter) to retrieve a single row of data from the server: 139 | 140 | ```csharp 141 | responseString = ""; 142 | try 143 | { 144 | WebClient client = AzureClient(); 145 | var response = client.DownloadData (getUrl); // GET 146 | // ...and wait... 147 | responseString = System.Text.Encoding.UTF8.GetString(response); 148 | Console.WriteLine ("GET => " + responseString); 149 | } 150 | catch (System.Net.WebException e) 151 | { 152 | Console.WriteLine("X-ZUMO-APPLICATION failed:" + e.Message); 153 | } 154 | ``` 155 | 156 | ## Converting C# to JSON 157 | 158 | When we are adding, updating, or deleting a record on the server, we need to take a C# object and convert it to a JSON representation to send in the HTTP message body. 159 | 160 | The `Todo` class definition below contains C# properties, and a method that manually creates a JSON string. 161 | 162 | ```csharp 163 | class Todo 164 | { 165 | public string Id {get;set;} 166 | public string Title {get;set;} 167 | public bool IsDone {get;set;} 168 | 169 | public string updatedAt=""; 170 | public string createdAt=""; 171 | public string version=""; 172 | public bool deleted=false; 173 | 174 | public string ToJson() 175 | { 176 | var json = ""; 177 | if (string.IsNullOrEmpty(Id)) // for inserting, do not specify primary key 178 | json = @"{""text"":"""+Title+@""",""complete"":"+IsDone.ToString().ToLower()+@"}"; 179 | else // for updating, must provide primary key 180 | json = @"{""id"":"""+Id+@""",""text"":"""+Title+@""",""complete"":"+IsDone.ToString().ToLower() + @"}"; 181 | // + $",\"updatedAt\":\"{updatedAt}\",\"createdAt\":\"{createdAt}\",\"version\":\"{version}\",\"deleted\":{deleted.ToString().ToLower()}}}"; 182 | return json; 183 | } 184 | } 185 | var t = new Todo {Title = "Test row 1", IsDone=false}; 186 | t.ToJson() 187 | ``` 188 | 189 | (there are libraries such as System.Json and the Json.NET nuget package that can help with this conversion) 190 | 191 | ## Insert 192 | 193 | Adding that object to the Azure Mobile Service can be done with a simple POST operation passing the JSON string as the body of the HTTP request: 194 | 195 | ```csharp 196 | try 197 | { 198 | WebClient client = AzureClient(); 199 | t.Id = ""; // ensure no primary key exists 200 | var payload = t.ToJson (); 201 | var response = client.UploadString (AddUrl, "POST", payload); // PATCH 202 | var responseString = response; 203 | 204 | 205 | Console.WriteLine ("INSERT SUCCESS, now it has an Id => " + responseString); 206 | } 207 | catch (System.Net.WebException e) 208 | { 209 | Console.WriteLine ("X-ZUMO-APPLICATION add failed " + e.Message); 210 | } 211 | ``` 212 | 213 | The `id` GUID is the primary key for this new row! 214 | 215 | ## Update 216 | 217 | Updating an existing row requires the primary key `id` to be passed in the querystring to identify the data being updated (and the JSON representing the new record being passed in the HTTP body). 218 | 219 | ```csharp 220 | t.Id = @"825b2f6e-95bd-4bbd-acff-656db772ed19"; // UPDATE THIS WITH AN ID FROM ABOVE 221 | var updateUrl = String.Format(UpdateUrl, t.Id); 222 | t.Title = "Test2"; 223 | t.IsDone = true; 224 | updateUrl 225 | ``` 226 | 227 | Using this URL, an HTTP PATCH request is made with the JSON representation of the updated data: 228 | 229 | ```csharp 230 | try 231 | { 232 | WebClient client = AzureClient(); 233 | var payload = t.ToJson (); 234 | var response = client.UploadString (updateUrl, "PATCH", payload); 235 | var responseString = response; 236 | Console.WriteLine ("UPDATE SUCCESS, new record => " + responseString); 237 | } 238 | catch (System.Net.WebException e) 239 | { 240 | Console.WriteLine ("X-ZUMO-APPLICATION update failed " + e.Message); 241 | } 242 | ``` 243 | 244 | ## Delete 245 | 246 | A record can be deleted by its primary key `id`. 247 | 248 | ```csharp 249 | t.Id = "89956c36-10bb-4d50-a87b-3fe6141ad082"; // UPDATE THIS FROM THE LIST ABOVE 250 | 251 | var deleteUrl = String.Format(UpdateUrl, t.Id); 252 | deleteUrl 253 | ``` 254 | 255 | The HTTP DELETE method is used to delete the row from the service: 256 | 257 | ```csharp 258 | WebClient client = AzureClient(); 259 | try 260 | { 261 | var payload = t.ToJson(); 262 | var response = client.UploadString(deleteUrl, "DELETE", ""); // DELETE, don't post the JSON body 263 | var responseString = response; 264 | 265 | Console.WriteLine("DELETE Json response => " + responseString); 266 | } 267 | catch (System.Net.WebException e) 268 | { 269 | if (e.Message.IndexOf("404") > 0) 270 | Console.WriteLine("Doesn't exist (previously deleted?) " + e.Message); 271 | else 272 | Console.WriteLine("X-ZUMO-APPLICATION delete failed " + e.Message); 273 | } 274 | ``` 275 | 276 | The first time a delete is executed the status code will be `200` to indicate the record was deleted. Subsequent calls to DELETE the same `id` will return a `404` status code, indicating that the record was "not found" for deletion. -------------------------------------------------------------------------------- /Azure/EasyTablesImages/portal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Azure/EasyTablesImages/portal.png -------------------------------------------------------------------------------- /Azure/EasyTablesImages/records.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Azure/EasyTablesImages/records.png -------------------------------------------------------------------------------- /Azure/EasyTablesImages/schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Azure/EasyTablesImages/schema.png -------------------------------------------------------------------------------- /Azure/EasyTablesImages/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Azure/EasyTablesImages/settings.png -------------------------------------------------------------------------------- /Azure/EasyTablesImages/setup1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Azure/EasyTablesImages/setup1.png -------------------------------------------------------------------------------- /Azure/EasyTablesWPF.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platform: WPF 4 | packages: [] 5 | --- 6 | 7 | # Playing around with Azure EasyTables 8 | 9 | This workbook shows how to access data stored in an **Azure Easy Table** (one of the new features of https://portal.azure.com) using simple REST principles. `WebClient` and some REST URLs is all that's required! The workbook will cover: 10 | 11 | * Configuration 12 | 13 | * List data 14 | 15 | * Filter data 16 | 17 | * Insert data 18 | 19 | * Update data 20 | 21 | * Delete data 22 | 23 | ## Configuration 24 | 25 | Sign-up for Azure and create an **App Service** that contains an **Easy Table**. This will include creating a **Database** and then an **Easy Table** with a **Schema**. The settings used in this workbook are: 26 | 27 | **App Service:** xamarin-todo-cd 28 | **Database:** xamarin-todo-cd_db (this is not visible to the consuming API) 29 | **Table Name:** todo 30 | **Schema columns:** text (string), complete (bool) 31 | 32 | ### Schema Config 33 | 34 | ![](EasyTablesImages/schema.png) 35 | 36 | ### Settings 37 | 38 | Start with the URL for the Azure App Service available in the Settings blade: 39 | 40 | ![](EasyTablesImages/settings.png) 41 | 42 | ```csharp 43 | static string subdomain = "xamarin-todo-cd"; // your subdomain 44 | ``` 45 | 46 | The REST API is similar to the old-style Azure Mobile Service [described on MSDN](https://msdn.microsoft.com/en-us/library/azure/jj677199.aspx) - use the name of the **Easy Table** in the URL (eg. `todo` in this case): 47 | 48 | ```csharp 49 | static string GetAllUrl = "https://" + subdomain + ".azurewebsites.net/tables/todo"; 50 | static string GetOneUrl = "https://" + subdomain + ".azurewebsites.net/tables/todo?$filter=(id%20eq%20%27{0}%27)"; 51 | static string AddUrl = "https://" + subdomain + ".azurewebsites.net/tables/todo"; // POST 52 | static string UpdateUrl = "https://" + subdomain + ".azurewebsites.net/tables/todo/{0}"; // PATCH 53 | static string DeleteUrl = "https://" + subdomain + ".azurewebsites.net/tables/todo/{0}"; // DELETE 54 | // yes, some of these are the same, the HTTP method [GET, POST, DELETE] differentiates them in the code 55 | ``` 56 | 57 | Finally, this helper method creates a correctly configured `WebClient` that includes the headers required to access the Azure Easy Table: 58 | 59 | ```csharp 60 | using System.Net; 61 | WebClient AzureClient () { 62 | WebClient client = new WebClient(); 63 | client.Headers.Add (HttpRequestHeader.Accept, "application/json"); 64 | client.Headers.Add (HttpRequestHeader.ContentType, "application/json"); 65 | client.Headers.Add ("ZUMO-API-VERSION", "2.0.0"); // for working with Easy Table 66 | return client; 67 | } 68 | ``` 69 | 70 | The examples below assume you've manually created a **todo** Easy Table in your Azure service, and added `text` and `complete` columns to the schema. 71 | 72 | ## List Data 73 | 74 | Using the WebClient we can manually set HTTP headers and access data stored in Azure. 75 | 76 | ```csharp 77 | var tasks = new List(); 78 | var responseString = ""; 79 | try 80 | { 81 | WebClient client = AzureClient(); 82 | var response = client.DownloadData (GetAllUrl); // GET 83 | // ...and wait... 84 | responseString = System.Text.Encoding.UTF8.GetString(response); 85 | } 86 | catch (System.Net.WebException e) 87 | { 88 | Console.WriteLine ("X-ZUMO-APPLICATION failed " + e.Message); 89 | } 90 | ``` 91 | 92 | The responseString contains the data returned from Azure in a JSON-formatted string: 93 | 94 | ```csharp 95 | responseString 96 | ``` 97 | 98 | The data can also be viewed in the **Easy Tables** blade of the Azure portal: 99 | 100 | ![](EasyTablesImages/records.png) 101 | 102 | ## Filter the list 103 | 104 | The URL scheme also supports filtering, so to restrict the list to *just completed tasks*, add a filter and send the request again: 105 | 106 | ```csharp 107 | var filterUrl = GetAllUrl + "?$filter=(complete%20eq%20true)"; // YOU COULD ALSO CHANGE THE FILTER 108 | responseString = ""; 109 | try 110 | { 111 | WebClient client = AzureClient(); 112 | var response = client.DownloadData (filterUrl); // GET 113 | responseString = System.Text.Encoding.UTF8.GetString(response); 114 | } 115 | catch (System.Net.WebException e) 116 | { 117 | Console.WriteLine("X-ZUMO-APPLICATION failed:" + e.Message); 118 | } 119 | ``` 120 | 121 | Now if we check the response, it is a shorter list! 122 | 123 | ```csharp 124 | responseString 125 | ``` 126 | 127 | ## Get a single item 128 | 129 | Individual items can be retrieved using a different REST URL. Copy a GUID from the previous response into the `id` variable. This `id` is added to the filter in the URL: 130 | 131 | ```csharp 132 | var id = @"a945b3f2-a9b8-4a04-a323-e6fc0bf255b3"; // UPDATE THIS WITH AN ID FROM ABOVE 133 | var getUrl = String.Format(GetOneUrl, id); 134 | getUrl 135 | ``` 136 | 137 | Now use this modified URL (including the filter) to retrieve a single row of data from the server: 138 | 139 | ```csharp 140 | responseString = ""; 141 | try 142 | { 143 | WebClient client = AzureClient(); 144 | var response = client.DownloadData (getUrl); // GET 145 | // ...and wait... 146 | responseString = System.Text.Encoding.UTF8.GetString(response); 147 | Console.WriteLine ("GET => " + responseString); 148 | } 149 | catch (System.Net.WebException e) 150 | { 151 | Console.WriteLine("X-ZUMO-APPLICATION failed:" + e.Message); 152 | } 153 | ``` 154 | 155 | ## Converting C# to JSON 156 | 157 | When we are adding, updating, or deleting a record on the server, we need to take a C# object and convert it to a JSON representation to send in the HTTP message body. 158 | 159 | The `Todo` class definition below contains C# properties, and a method that manually creates a JSON string. 160 | 161 | ```csharp 162 | class Todo 163 | { 164 | public string Id {get;set;} 165 | public string Title {get;set;} 166 | public bool IsDone {get;set;} 167 | 168 | public string updatedAt=""; 169 | public string createdAt=""; 170 | public string version=""; 171 | public bool deleted=false; 172 | 173 | public string ToJson() 174 | { 175 | var json = ""; 176 | if (string.IsNullOrEmpty(Id)) // for inserting, do not specify primary key 177 | json = @"{""text"":"""+Title+@""",""complete"":"+IsDone.ToString().ToLower()+@"}"; 178 | else // for updating, must provide primary key 179 | json = @"{""id"":"""+Id+@""",""text"":"""+Title+@""",""complete"":"+IsDone.ToString().ToLower() + @"}"; 180 | // + $",\"updatedAt\":\"{updatedAt}\",\"createdAt\":\"{createdAt}\",\"version\":\"{version}\",\"deleted\":{deleted.ToString().ToLower()}}}"; 181 | return json; 182 | } 183 | } 184 | var t = new Todo {Title = "Test row 1", IsDone=false}; 185 | t.ToJson() 186 | ``` 187 | 188 | \(there are libraries such as System.Json and the Json.NET nuget package that can help with this conversion) 189 | 190 | ## Insert 191 | 192 | Adding that object to the Azure Mobile Service can be done with a simple POST operation passing the JSON string as the body of the HTTP request: 193 | 194 | ```csharp 195 | try 196 | { 197 | WebClient client = AzureClient(); 198 | t.Id = ""; // ensure no primary key exists 199 | var payload = t.ToJson (); 200 | var response = client.UploadString (AddUrl, "POST", payload); // PATCH 201 | var responseString = response; 202 | 203 | 204 | Console.WriteLine ("INSERT SUCCESS, now it has an Id => " + responseString); 205 | } 206 | catch (System.Net.WebException e) 207 | { 208 | Console.WriteLine ("X-ZUMO-APPLICATION add failed " + e.Message); 209 | } 210 | ``` 211 | 212 | The `id` GUID is the primary key for this new row! 213 | 214 | ## Update 215 | 216 | Updating an existing row requires the primary key `id` to be passed in the querystring to identify the data being updated (and the JSON representing the new record being passed in the HTTP body). 217 | 218 | ```csharp 219 | t.Id = @"a945b3f2-a9b8-4a04-a323-e6fc0bf255b3"; // UPDATE THIS WITH AN ID FROM ABOVE 220 | var updateUrl = String.Format(UpdateUrl, t.Id); 221 | t.Title = "Test2"; 222 | t.IsDone = true; 223 | updateUrl 224 | ``` 225 | 226 | Using this URL, an HTTP PATCH request is made with the JSON representation of the updated data: 227 | 228 | ```csharp 229 | try 230 | { 231 | WebClient client = AzureClient(); 232 | var payload = t.ToJson (); 233 | var response = client.UploadString (updateUrl, "PATCH", payload); 234 | var responseString = response; 235 | Console.WriteLine ("UPDATE SUCCESS, changed record => " + responseString); 236 | } 237 | catch (System.Net.WebException e) 238 | { 239 | Console.WriteLine ("X-ZUMO-APPLICATION update failed " + e.Message); 240 | } 241 | ``` 242 | 243 | ## Delete 244 | 245 | A record can be deleted by its primary key `id`. 246 | 247 | ```csharp 248 | t.Id = "a945b3f2-a9b8-4a04-a323-e6fc0bf255b3"; // UPDATE THIS FROM THE LIST ABOVE 249 | 250 | var deleteUrl = String.Format(UpdateUrl, t.Id); 251 | deleteUrl 252 | ``` 253 | 254 | The HTTP DELETE method is used to delete the row from the service: 255 | 256 | ```csharp 257 | WebClient client = AzureClient(); 258 | try 259 | { 260 | var payload = t.ToJson(); 261 | var response = client.UploadString(deleteUrl, "DELETE", ""); // DELETE, don't post the JSON body 262 | var responseString = response; 263 | 264 | Console.WriteLine("DELETE Json response => " + responseString); 265 | } 266 | catch (System.Net.WebException e) 267 | { 268 | if (e.Message.IndexOf("404") > 0) 269 | Console.WriteLine("Doesn't exist (previously deleted?) " + e.Message); 270 | else 271 | Console.WriteLine("X-ZUMO-APPLICATION delete failed " + e.Message); 272 | } 273 | ``` 274 | 275 | The first time a delete is executed the status code will be `200` to indicate the record was deleted. Subsequent calls to DELETE the same `id` will return a `404` status code, indicating that the record was "not found" for deletion. 276 | 277 | -------------------------------------------------------------------------------- /Azure/MobileService101.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platforms: 4 | - MacNet45 5 | --- 6 | 7 | # Azure First Principles 8 | 9 | This workbook shows how to access data stored on **Azure Mobile Services** [manage.windowsazure.com](https://manage.windowsazure.com) (the old-style Azure) using simple REST principles. `WebClient` and some REST URLs is all that's required! The workbook will cover: 10 | 11 | * Configuration 12 | 13 | * List data 14 | 15 | * Filter data 16 | 17 | * Insert data 18 | 19 | * Update data 20 | 21 | * Delete data 22 | 23 | ## Configuration 24 | 25 | Start with constants for the authentication credentials from the Azure portal. These are available in the **Dashboard** for an Azure Mobile Service. 26 | 27 | ```csharp 28 | static string subdomain = "xamarin-todo-cd2"; // your subdomain 29 | static string MobileServiceAppId = ""; // your application key 30 | ``` 31 | 32 | The REST API is [described on MSDN](https://msdn.microsoft.com/en-us/library/azure/jj677199.aspx) 33 | 34 | ```csharp 35 | static string GetAllUrl = "https://" + subdomain + ".azure-mobile.net/tables/TodoItem"; 36 | static string GetOneUrl = "https://" + subdomain + ".azure-mobile.net/tables/TodoItem?$filter=(id%20eq%20%27{0}%27)"; 37 | static string AddUrl = "https://" + subdomain + ".azure-mobile.net/tables/TodoItem"; 38 | static string UpdateUrl = "https://" + subdomain + ".azure-mobile.net/tables/TodoItem/{0}"; 39 | static string DeleteUrl = "https://" + subdomain + ".azure-mobile.net/tables/TodoItem/{0}"; 40 | // yes, some of these are the same, the HTTP method [GET, POST, DELETE] differentiates them in the code 41 | ``` 42 | 43 | Finally, this helper method creates a correctly configured `WebClient` that includes the headers required to access the Azure Mobile Service (especially the `MobileServiceAppId` which is specific to your instance): 44 | 45 | ```csharp 46 | using System.Net; 47 | WebClient AzureClient () { 48 | WebClient client = new WebClient(); 49 | client.Headers.Add (HttpRequestHeader.Accept, "application/json"); 50 | client.Headers.Add (HttpRequestHeader.ContentType, "application/json"); 51 | client.Headers.Add ("X-ZUMO-APPLICATION", MobileServiceAppId); 52 | return client; 53 | } 54 | ``` 55 | 56 | The examples below assume you've configured the **Todo** demo (which creates a `TodoItem` table in your Azure service). 57 | 58 | ## List Data 59 | 60 | Using the WebClient we can manually set HTTP headers and access data stored in Azure. 61 | 62 | ```csharp 63 | var tasks = new List(); 64 | var responseString = ""; 65 | try 66 | { 67 | WebClient client = AzureClient(); 68 | var response = client.DownloadData (GetAllUrl); // GET 69 | // ...and wait... 70 | responseString = System.Text.Encoding.UTF8.GetString(response); 71 | } 72 | catch (System.Net.WebException e) 73 | { 74 | Console.WriteLine ("X-ZUMO-APPLICATION failed" + e.Message); 75 | } 76 | ``` 77 | 78 | The responseString contains the data returned from Azure in a JSON-formatted string: 79 | 80 | ```csharp 81 | responseString 82 | ``` 83 | 84 | ## Filter the list 85 | 86 | The URL scheme also supports filtering, so to restrict the list to *just completed tasks*, add a filter and send the request again: 87 | 88 | ```csharp 89 | var filterUrl = GetAllUrl + "?$filter=(complete%20eq%20true)"; // YOU COULD ALSO CHANGE THE FILTER 90 | responseString = ""; 91 | try 92 | { 93 | WebClient client = AzureClient(); 94 | var response = client.DownloadData (filterUrl); // GET 95 | responseString = System.Text.Encoding.UTF8.GetString(response); 96 | } 97 | catch (System.Net.WebException e) 98 | { 99 | Console.WriteLine("X-ZUMO-APPLICATION failed:" + e.Message); 100 | } 101 | ``` 102 | 103 | Now if we check the response, it is a shorter list! 104 | 105 | ```csharp 106 | responseString 107 | ``` 108 | 109 | ## Get a single item 110 | 111 | Individual items can be retrieved using a different REST URL. Copy a GUID from the previous response into the `id` variable. This `id` is added to the filter in the URL: 112 | 113 | ```csharp 114 | var id = @"C066834E-4CA2-4804-B746-6DCD14B45CC5"; // UPDATE THIS WITH AN ID FROM ABOVE 115 | var getUrl = String.Format(GetOneUrl, id); 116 | getUrl 117 | ``` 118 | 119 | Now use this modified URL (including the filter) to retrieve a single row of data from the server: 120 | 121 | ```csharp 122 | responseString = ""; 123 | try 124 | { 125 | WebClient client = AzureClient(); 126 | var response = client.DownloadData (getUrl); // GET 127 | // ...and wait... 128 | responseString = System.Text.Encoding.UTF8.GetString(response); 129 | Console.WriteLine ("GET => " + responseString); 130 | } 131 | catch (System.Net.WebException e) 132 | { 133 | Console.WriteLine("X-ZUMO-APPLICATION failed:" + e.Message); 134 | } 135 | ``` 136 | 137 | ## Converting C# to JSON 138 | 139 | When we are adding, updating, or deleting a record on the server, we need to take a C# object and convert it to a JSON representation to send in the HTTP message body. 140 | 141 | The `Todo` class definition below contains C# properties, and a method that manually creates a JSON string. 142 | 143 | ```csharp 144 | class Todo 145 | { 146 | public string Id {get;set;} 147 | public string Title {get;set;} 148 | public bool IsDone {get;set;} 149 | 150 | public string ToJson() 151 | { 152 | var json = ""; 153 | if (string.IsNullOrEmpty(Id)) // for inserting, do not specify primary key 154 | json = @"{""text"":"""+Title+@""",""complete"":"+IsDone.ToString().ToLower()+@"}"; 155 | else // for updating, must provide primary key 156 | json = @"{""id"":"""+Id+@""",""text"":"""+Title+@""",""complete"":"+IsDone.ToString().ToLower()+@"}"; 157 | return json; 158 | } 159 | } 160 | var t = new Todo {Title = "Test row 1", IsDone=false}; 161 | t.ToJson() 162 | ``` 163 | 164 | (there are libraries such as System.Json and the Json.NET nuget package that can help with this conversion) 165 | 166 | ## Insert 167 | 168 | Adding that object to the Azure Mobile Service can be done with a simple POST operation passing the JSON string as the body of the HTTP request: 169 | 170 | ```csharp 171 | try 172 | { 173 | WebClient client = AzureClient(); 174 | var payload = t.ToJson (); 175 | var response = client.UploadString (AddUrl, "POST", payload); // PATCH 176 | var responseString = response; 177 | 178 | Console.WriteLine ("INSERT SUCCESS, now it has an Id => " + responseString); 179 | } 180 | catch (System.Net.WebException e) 181 | { 182 | Console.WriteLine ("X-ZUMO-APPLICATION add failed" + e.Message); 183 | } 184 | ``` 185 | 186 | The `id` GUID is the primary key for this new row! 187 | 188 | ## Update 189 | 190 | Updating an existing row requires the primary key `id` to be passed in the querystring to identify the data being updated (and the JSON representing the new record being passed in the HTTP body). 191 | 192 | ```csharp 193 | t.Id = @"6854CAA6-C614-4B5F-9AAB-83AEC6C7FA7F"; // UPDATE THIS WITH AN ID FROM ABOVE 194 | var updateUrl = String.Format(UpdateUrl, t.Id); 195 | t.Title = "Test2"; 196 | t.IsDone = true; 197 | updateUrl 198 | ``` 199 | 200 | Using this URL, an HTTP PATCH request is made with the JSON representation of the updated data: 201 | 202 | ```csharp 203 | try 204 | { 205 | WebClient client = AzureClient(); 206 | var payload = t.ToJson (); 207 | var response = client.UploadString (updateUrl, "PATCH", payload); 208 | var responseString = response; 209 | Console.WriteLine ("UPDATE SUCCESS, new record => " + responseString); 210 | } 211 | catch (System.Net.WebException e) 212 | { 213 | Console.WriteLine ("X-ZUMO-APPLICATION update failed" + e.Message); 214 | } 215 | ``` 216 | 217 | ## Delete 218 | 219 | A record can be deleted by its primary key `id`. 220 | 221 | ```csharp 222 | var deleteId = "6854CAA6-C614-4B5F-9AAB-83AEC6C7FA7F"; // UPDATE THIS WITH AN ID FROM ABOVE 223 | var deleteUrl = String.Format(UpdateUrl, deleteId); 224 | deleteUrl 225 | ``` 226 | 227 | The HTTP DELETE method is used to delete the row from the service: 228 | 229 | ```csharp 230 | WebClient client = AzureClient(); 231 | try 232 | { 233 | var payload = t.ToJson(); 234 | var response = client.UploadString(deleteUrl, "DELETE", payload); // DELETE 235 | // ...and wait... 236 | var responseString = response; 237 | 238 | Console.WriteLine("DELETE Json response => " + (responseString==""?"success":responseString)); 239 | } 240 | catch (System.Net.WebException e) 241 | { 242 | if (e.Message.IndexOf("404") > 0) 243 | Console.WriteLine("Doesn't exist (previously deleted?) " + e.Message); 244 | else 245 | Console.WriteLine("X-ZUMO-APPLICATION delete failed " + e.Message); 246 | } 247 | ``` 248 | 249 | The first time a delete is executed the status code will be `200` to indicate the record was deleted. Subsequent calls to DELETE the same `id` will return a `404` status code, indicating that the record was "not found" for deletion. -------------------------------------------------------------------------------- /Azure/Screenshots/azure-configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Azure/Screenshots/azure-configuration.png -------------------------------------------------------------------------------- /Azure/Screenshots/azure-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Azure/Screenshots/azure-filter.png -------------------------------------------------------------------------------- /Azure/TryAzure-Mac.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platforms: 4 | - MacNet45 5 | packages: 6 | - id: Newtonsoft.Json 7 | version: 9.0.1 8 | - id: Microsoft.Net.Http 9 | version: 2.2.29 10 | - id: Microsoft.Azure.Mobile.Client 11 | version: 3.0.1 12 | - id: Microsoft.Bcl 13 | version: 1.1.10 14 | --- 15 | 16 | # Azure TryAppService 17 | 18 | Two NuGets - **Microsoft.Azure.Mobile.Client** & Newtonsoft.Json - have been added and require these references: 19 | 20 | ```csharp 21 | #r "Newtonsoft.Json" 22 | #r "Microsoft.WindowsAzure.Mobile" 23 | #r "Microsoft.WindowsAzure.Mobile.Ext" 24 | ``` 25 | 26 | Use the [tryappservice.azure.com](https://tryappservice.azure.com/ "tryappservice.azure.com") **Mobile App > TodoList** to create a free back-end to test. This class matches the mobile app backend that is automatically created: 27 | 28 | ```csharp 29 | using Newtonsoft.Json; 30 | using Microsoft.WindowsAzure.MobileServices; 31 | 32 | public class TodoItem 33 | { 34 | [JsonProperty(PropertyName = "id")] 35 | public string ID {get;set;} 36 | [JsonProperty(PropertyName = "text")] 37 | public string Name {get;set;} 38 | [JsonProperty(PropertyName = "complete")] 39 | public bool Done {get;set;} 40 | [Version] 41 | public string Version { get; set; } 42 | public override string ToString() { 43 | return $"{Name} is " + (Done?"done":"not done"); 44 | } 45 | } 46 | ``` 47 | 48 | Replace the URL below with your temporary, generated endpoint URL: 49 | 50 | ```csharp 51 | var mobileService = new MobileServiceClient ("https://da0cfa57-0ee0-4-231-b9ee.azurewebsites.net/"); 52 | var table = mobileService.GetTable (); 53 | ``` 54 | 55 | Now it’s possible to interact with the **Azure App Service**. The following code creates a new `TodoItem` class, inserts it into the table on the server, and retrieves the list from the server: 56 | 57 | ```csharp 58 | var rememberTo = new TodoItem {Name="buy apples"}; 59 | await table.InsertAsync (rememberTo); 60 | List todos = await table.Take (10).ToListAsync (); 61 | ``` 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Azure/TryAzure-WPF.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platform: WPF 4 | packages: 5 | - id: Microsoft.Bcl 6 | version: 1.1.10 7 | - id: Newtonsoft.Json 8 | version: 9.0.1 9 | - id: Microsoft.Net.Http 10 | version: 2.2.29 11 | - id: Microsoft.Azure.Mobile.Client 12 | version: 3.0.1 13 | --- 14 | 15 | # Azure TryAppService 16 | 17 | Two NuGets - **Microsoft.Azure.Mobile.Client** & Newtonsoft.Json - have been added and require these references: 18 | 19 | ```csharp 20 | #r "Newtonsoft.Json" 21 | #r "Microsoft.WindowsAzure.Mobile" 22 | #r "Microsoft.WindowsAzure.Mobile.Ext" 23 | ``` 24 | 25 | Use the [tryappservice.azure.com](https://tryappservice.azure.com/ "tryappservice.azure.com") **Mobile App > TodoList **to create a free back-end to test. This class matches the mobile app backend that is automatically created: 26 | 27 | ```csharp 28 | using Newtonsoft.Json; 29 | using Microsoft.WindowsAzure.MobileServices; 30 | 31 | public class TodoItem 32 | { 33 | [JsonProperty(PropertyName = "id")] 34 | public string ID {get;set;} 35 | [JsonProperty(PropertyName = "text")] 36 | public string Name {get;set;} 37 | [JsonProperty(PropertyName = "complete")] 38 | public bool Done {get;set;} 39 | [Version] 40 | public string Version { get; set; } 41 | public override string ToString() { 42 | return $"{Name} is " + (Done?"done":"not done"); 43 | } 44 | } 45 | ``` 46 | 47 | Replace the URL below with your temporary, generated endpoint URL: 48 | 49 | ```csharp 50 | var mobileService = new MobileServiceClient ("https://da0cfa57-0ee0-4-231-b9ee.azurewebsites.net/"); 51 | var table = mobileService.GetTable (); 52 | ``` 53 | 54 | Now it’s possible to interact with the **Azure App Service**. The following code creates a new `TodoItem` class, inserts it into the table on the server, and retrieves the list from the server: 55 | 56 | ```csharp 57 | var rememberTo = new TodoItem {Name="buy apples"}; 58 | await table.InsertAsync (rememberTo); 59 | List todos = await table.Take (10).ToListAsync (); 60 | ``` 61 | 62 | -------------------------------------------------------------------------------- /Azure/TryAzure-iOS.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platform: iOS 4 | packages: 5 | - id: Microsoft.Bcl 6 | version: 1.1.10 7 | - id: Microsoft.Net.Http 8 | version: 2.2.29 9 | - id: Microsoft.Azure.Mobile.Client 10 | version: 3.0.1 11 | - id: Newtonsoft.Json 12 | version: 9.0.1 13 | --- 14 | 15 | # Azure TryAppService 16 | 17 | Two NuGets - **Microsoft.Azure.Mobile.Client** & Newtonsoft.Json - have been added and require these references: 18 | 19 | ```csharp 20 | #r "Newtonsoft.Json" 21 | #r "Microsoft.WindowsAzure.Mobile" 22 | #r "Microsoft.WindowsAzure.Mobile.Ext" 23 | ``` 24 | 25 | Use the [tryappservice.azure.com](https://tryappservice.azure.com/ "tryappservice.azure.com") **Mobile App > TodoList** to create a free back-end to test. This class matches the mobile app backend that is automatically created: 26 | 27 | ```csharp 28 | using Newtonsoft.Json; 29 | using Microsoft.WindowsAzure.MobileServices; 30 | 31 | public class TodoItem 32 | { 33 | [JsonProperty(PropertyName = "id")] 34 | public string ID {get;set;} 35 | [JsonProperty(PropertyName = "text")] 36 | public string Name {get;set;} 37 | [JsonProperty(PropertyName = "complete")] 38 | public bool Done {get;set;} 39 | [Version] 40 | public string Version { get; set; } 41 | public override string ToString() { 42 | return $"{Name} is " + (Done?"done":"not done"); 43 | } 44 | } 45 | ``` 46 | 47 | Replace the URL below with your temporary, generated endpoint URL: 48 | 49 | ```csharp 50 | var mobileService = new MobileServiceClient ("https://da0cfa57-0ee0-4-231-b9ee.azurewebsites.net/"); 51 | var table = mobileService.GetTable (); 52 | ``` 53 | 54 | Now it’s possible to interact with the **Azure App Service**. The following code creates a new `TodoItem` class, inserts it into the table on the server, and retrieves the list from the server: 55 | 56 | ```csharp 57 | var rememberTo = new TodoItem {Name="buy apples"}; 58 | await table.InsertAsync (rememberTo); 59 | List todos = await table.Take (10).ToListAsync (); 60 | ``` 61 | -------------------------------------------------------------------------------- /Collections/Arrays.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platforms: 4 | - Console 5 | --- 6 | 7 | # Collections: Arrays 8 | 9 | An array is a simple data structure: 10 | 11 | * it stores a collection of data elements, 12 | * it has a fixed size, 13 | * it only contains elements that match the declared type. 14 | 15 | ## Initialization Syntax 16 | 17 | The easiest way to create an array with known values 18 | is using C#'s initialization syntax: 19 | 20 | ```csharp 21 | int[] primes = {2,3,5,7,11,13,17,19,31,37,41,43,47,53,59,61,67,71} 22 | ``` 23 | 24 | Each value in the array has an `index`, and we can get a value 25 | by its index: 26 | 27 | ```csharp 28 | primes[9] // prints the 10th prime - arrays are zero-based! 29 | ``` 30 | 31 | ## Create a fixed size array 32 | 33 | Another way to create an array is to declare the array 34 | with a known size (20 elements in this example), and 35 | then set each value using the index (remember it starts at zero): 36 | 37 | ```csharp 38 | var squares = new int[20]; // 20 elements with default(int) value 39 | for (var i = 0; i <20; i++) { 40 | squares[i] = i*i; 41 | } 42 | 43 | squares 44 | ``` 45 | 46 | The values in the array can then be retrieved by their index: 47 | 48 | ```csharp 49 | squares[4] // 5th square 50 | ``` 51 | 52 | The value at an index can also be set to a different value: 53 | 54 | ```csharp 55 | squares[4] = 444 // replace the value in index '4' 56 | ``` 57 | 58 | ## Declare and then set 59 | 60 | If the size of the array is initially unknown, an 61 | array variable can be declared without specifying the size. 62 | 63 | ```csharp 64 | string[] dwarves; 65 | // ... 66 | dwarves = new string[4]; 67 | dwarves[0] = "Sleepy"; 68 | dwarves[1] = "Sneezy"; 69 | dwarves[2] = "Dopey"; 70 | dwarves[3] = "Doc"; 71 | 72 | dwarves 73 | ``` 74 | 75 | This highlights a problem with arrays – once the 76 | size has been declared, we can't easily change it 77 | to add another three dwarves: 78 | 79 | `dwarves[4] = "Bashful"` 80 | 81 | will result in an `IndexOutOfRangeException`. The only 82 | option is to assign a totally new array to the variable: 83 | 84 | ```csharp 85 | dwarves = new string[7]; 86 | dwarves[0] = "Sleepy"; 87 | dwarves[1] = "Sneezy"; 88 | dwarves[2] = "Dopey"; 89 | dwarves[3] = "Doc"; 90 | dwarves[4] = "Bashful"; 91 | dwarves[5] = "Grumpy"; 92 | dwarves[6] = "Happy"; 93 | 94 | dwarves 95 | ``` 96 | 97 | ## Not just simple Types 98 | 99 | Arrays can also store objects, not just `string` and `int`: 100 | 101 | ```csharp 102 | class Person { 103 | public string FirstName {get;set;} 104 | public string LastName {get;set;} 105 | public override string ToString() => $"{FirstName} {LastName}"; 106 | } 107 | var goodguys = new Person[2]; 108 | goodguys[0] = new Person {FirstName="Snow",LastName="White"}; 109 | goodguys[1] = new Person {FirstName="Humbert",LastName="Huntsman"}; 110 | ``` 111 | 112 | ```csharp 113 | goodguys // view the array data 114 | ``` 115 | 116 | ## Pros & Cons of Arrays 117 | 118 | ### Pros 119 | 120 | * Simple and easy to understand 121 | * Fast and efficient 122 | 123 | ### Cons 124 | 125 | * Cannot be resized 126 | * Elements cannot be added or removed 127 | 128 | Other collection types like `List` and `Dictionary` address some 129 | of the array's limitations. 130 | -------------------------------------------------------------------------------- /Collections/README.md: -------------------------------------------------------------------------------- 1 | Collections: Arrays (Inspector) 2 | ========= 3 | 4 | Quick explanation of how C# arrays work. 5 | 6 | 7 | ![](Screenshots/arrays-inspector.png) 8 | -------------------------------------------------------------------------------- /Collections/Screenshots/arrays-inspector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Collections/Screenshots/arrays-inspector.png -------------------------------------------------------------------------------- /Csharp6/Arrays.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platforms: 4 | - Console 5 | --- 6 | 7 | # Arrays 8 | 9 | Workbooks conversion of an [MSDN tutorial on Arrays](https://msdn.microsoft.com/en-us/library/aa288453(v=vs.71).aspx). 10 | 11 | ## Tutorial 12 | 13 | This tutorial is divided into the following sections: 14 | 15 | * Arrays in General 16 | 17 | * Declaring Arrays 18 | 19 | * Initializing Arrays 20 | 21 | * Accessing Array Members 22 | 23 | * Arrays are Objects 24 | 25 | * Using foreach with Arrays 26 | 27 | ### Arrays in General 28 | 29 | C# arrays are zero indexed; that is, the array indexes start at zero. Arrays in C# work similarly to how arrays work in most other popular languages There are, however, a few differences that you should be aware of. 30 | 31 | When declaring an array, the square brackets (\[\]) must come after the type, not the identifier. Placing the brackets after the identifier is not legal syntax in C#. 32 | 33 | ```csharp 34 | int[] table; // not int table[]; 35 | ``` 36 | 37 | Another detail is that the size of the array is not part of its type as it is in the C language. This allows you to declare an array and assign any array of **int** objects to it, regardless of the array's length. 38 | 39 | ```csharp 40 | int[] numbers; // declare numbers as an int array of any size 41 | numbers = new int[10]; // numbers is a 10-element array 42 | numbers = new int[20]; // now it's a 20-element array 43 | numbers 44 | ``` 45 | 46 | ### Declaring Arrays 47 | 48 | C# supports single-dimensional arrays, multidimensional arrays (rectangular arrays), and array-of-arrays (jagged arrays). The following examples show how to declare different kinds of arrays: 49 | 50 | ```csharp 51 | // Single dimension 52 | int[] numbers; 53 | // Multi dimension 54 | string[,] names; 55 | // Array of arrays (jagged) 56 | byte[][] scores; 57 | ``` 58 | 59 | Declaring them (as shown above) does not actually create the arrays. In C#, arrays are objects (discussed later in this tutorial) and must be instantiated. The following examples show how to create arrays: 60 | 61 | ```csharp 62 | // Single dimension 63 | int[] numbers = new int[5]; 64 | // Multi dimension 65 | string[,] names = new string[5,4]; 66 | // Array of arrays (jagged) 67 | byte[][] scores = new byte[5][]; 68 | for (int x = 0; x < scores.Length; x++) 69 | { 70 | scores[x] = new byte[4]; 71 | } 72 | ``` 73 | 74 | You can even mix rectangular and jagged arrays. For example, the following code declares a single-dimensional array of three-dimensional arrays of two-dimensional arrays of type **int**: 75 | 76 | ```csharp 77 | int[,,] buttons = new int[4,5,3]; 78 | int[][,,][,] numbers; 79 | ``` 80 | 81 | #### Example 82 | 83 | The following is a complete C# program that declares and instantiates arrays as discussed above. 84 | 85 | ```csharp 86 | // Single-dimensional array 87 | int[] numbers = new int[5]; 88 | 89 | // Multidimensional array 90 | string[,] names = new string[5,4]; 91 | 92 | // Array-of-arrays (jagged array) 93 | byte[][] scores = new byte[5][]; 94 | 95 | // Create the jagged array 96 | for (int i = 0; i < scores.Length; i++) 97 | { 98 | scores[i] = new byte[i+3]; 99 | } 100 | 101 | // Print length of each row 102 | for (int i = 0; i < scores.Length; i++) 103 | { 104 | Console.WriteLine("Length of row {0} is {1}", i, scores[i].Length); 105 | } 106 | ``` 107 | 108 | ### Initializing Arrays 109 | 110 | C# provides simple and straightforward ways to initialize arrays at declaration time by enclosing the initial values in curly braces ({}). The following examples show different ways to initialize different kinds of arrays. 111 | 112 | > **Note **If you do not initialize an array at the time of declaration, the array members are automatically initialized to the default initial value for the array type. Also, if you declare the array as a field of a type, it will be set to the default value null when you instantiate the type. 113 | 114 | #### Single-Dimension Array 115 | 116 | ```csharp 117 | int[] numbers1 = new int[5] {1, 2, 3, 4, 5}; 118 | string[] names1 = new string[3] {"Matt", "Joanne", "Robert"}; 119 | 120 | // You can omit the size when an initializer is provided 121 | int[] numbers2 = new int[] {1, 2, 3, 4, 5}; 122 | string[] names2 = new string[] {"Matt", "Joanne", "Robert"}; 123 | 124 | // You can also omit the new when an initializer is provided 125 | int[] numbers3 = {1, 2, 3, 4, 5}; 126 | string[] names3 = {"Matt", "Joanne", "Robert"}; 127 | ``` 128 | 129 | #### Multi dimension Array 130 | 131 | ```csharp 132 | int[,] numbers1 = new int[3, 2] { {1, 2}, {3, 4}, {5, 6} }; 133 | string[,] siblings1 = new string[2, 2] { {"Mike","Amy"}, {"Mary","Albert"} }; 134 | 135 | // You can omit the size when an initializer is provided 136 | int[,] numbers2 = new int[,] { {1, 2}, {3, 4}, {5, 6} }; 137 | string[,] siblings2 = new string[,] { {"Mike","Amy"}, {"Mary","Albert"} }; 138 | 139 | // You can also omit the new when an initializer is provided 140 | int[,] numbers3 = { {1, 2}, {3, 4}, {5, 6} }; 141 | string[,] siblings3 = { {"Mike", "Amy"}, {"Mary", "Albert"} }; 142 | ``` 143 | 144 | #### Jagged Array (Array of arrays) 145 | 146 | ```csharp 147 | int[][] numbers1 = new int[2][] { new int[] {2,3,4}, new int[] {5,6,7,8,9} }; 148 | 149 | // You can also omit the size of the first array, like this: 150 | int[][] numbers2 = new int[][] { new int[] {2,3,4}, new int[] {5,6,7,8,9} }; 151 | // or 152 | int[][] numbers3 = { new int[] {2,3,4}, new int[] {5,6,7,8,9} }; 153 | ``` 154 | 155 | Notice that there is no initialization syntax for the elements of a jagged array. 156 | 157 | ### Accessing Array Members 158 | 159 | Accessing array members is straightforward and similar to how you access array members in C/C\+\+. For example, the following code creates an array called `numbers` and then assigns a `5` to the fifth element of the array: 160 | 161 | ```csharp 162 | int[] numbers = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; 163 | numbers[4] = 5; 164 | ``` 165 | 166 | The following code declares a multidimensional array and assigns `5` to the member located at `[1, 1]`: 167 | 168 | ```csharp 169 | int[,] numbers = { {1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10} }; 170 | numbers[1, 1] 171 | ``` 172 | 173 | ```csharp 174 | numbers[1, 1] = 5; 175 | ``` 176 | 177 | The following is a declaration of a single-dimension jagged array that contains two elements. The first element is an array of two integers, and the second is an array of three integers: 178 | 179 | ```csharp 180 | int[][] numbers = new int[][] { new int[] {1, 2}, new int[] {3, 4, 5}}; 181 | ``` 182 | 183 | The following statements assign 58 to the first element of the first array and 667 to the second element of the second array: 184 | 185 | ```csharp 186 | numbers[0][0] = 58; 187 | numbers[1][1] = 667; 188 | numbers 189 | ``` 190 | 191 | ### Arrays are Objects 192 | 193 | In C#, arrays are actually objects. **System.Array** is the abstract base type of all array types. You can use the properties, and other class members, that **System.Array** has. An example of this would be using the Length** **property to get the length of an array. The following code assigns the length of the `numbers` array, which is `5`, to a variable called `LengthOfNumbers`: 194 | 195 | ```csharp 196 | int[] numbers = {1, 2, 3, 4, 5}; 197 | int LengthOfNumbers = numbers.Length; 198 | ``` 199 | 200 | The **System.Array** class provides many other useful methods/properties, such as methods for sorting, searching, and copying arrays. 201 | 202 | ### Using foreach on Arrays 203 | 204 | C# also provides the **foreach** statement. This statement provides a simple, clean way to iterate through the elements of an array. For example, the following code creates an array called `numbers` and iterates through it with the **foreach** statement: 205 | 206 | ```csharp 207 | int[] numbers = {4, 5, 6, 1, 2, 3, -2, -1, 0}; 208 | foreach (int i in numbers) 209 | { 210 | System.Console.WriteLine(i); 211 | } 212 | ``` 213 | 214 | With multidimensional arrays, you can use the same method to iterate through the elements, for example: 215 | 216 | ```csharp 217 | int[,] numbers = new int[3, 2] {{9, 99}, {3, 33}, {5, 55}}; 218 | foreach(int i in numbers) 219 | { 220 | Console.Write("{0} ", i); 221 | } 222 | ``` 223 | 224 | However, with multidimensional arrays, using a nested **for** loop gives you more control over the array elements. -------------------------------------------------------------------------------- /Csharp6/OperatorOverloading.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platforms: 4 | - Console 5 | --- 6 | 7 | # Operator Overloading 8 | 9 | Workbooks conversion of an [MSDN tutorial on Operator Overloading](https://msdn.microsoft.com/en-us/library/aa288467(v=vs.71).aspx). 10 | 11 | ## Tutorial 12 | 13 | Operator overloading permits user-defined operator implementations to be specified for operations where one or both of the operands are of a user-defined class or struct type. The tutorial contains two examples. The first example shows how to use operator overloading to create a complex number class that defines complex addition. The second example shows how to use operator overloading to implement a three-valued logical type. 14 | 15 | #### Example 1 16 | 17 | This example shows how you can use operator overloading to create a complex number class `Complex` that defines complex addition. The program displays the imaginary and the real parts of the numbers and the addition result using an override of the `ToString` method. 18 | 19 | ```csharp 20 | public struct Complex 21 | { 22 | public int real; 23 | public int imaginary; 24 | 25 | public Complex(int real, int imaginary) 26 | { 27 | this.real = real; 28 | this.imaginary = imaginary; 29 | } 30 | 31 | // Declare which operator to overload (+), the types 32 | // that can be added (two Complex objects), and the 33 | // return type (Complex): 34 | public static Complex operator +(Complex c1, Complex c2) 35 | { 36 | return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary); 37 | } 38 | // Override the ToString method to display an complex number in the suitable format: 39 | public override string ToString() 40 | { 41 | return $"{real} + {imaginary}i"; 42 | } 43 | } 44 | ``` 45 | 46 | Test the overloaded operator: 47 | 48 | ```csharp 49 | Complex num1 = new Complex(2,3); 50 | Complex num2 = new Complex(3,4); 51 | 52 | // Add two Complex objects (num1 and num2) through the 53 | // overloaded plus operator: 54 | Complex sum = num1 + num2; 55 | 56 | // Print the numbers and the sum using the overriden ToString method: 57 | Console.WriteLine($"First complex number: {num1}"); 58 | Console.WriteLine($"Second complex number: {num2}"); 59 | Console.WriteLine($"The sum of the two numbers: {sum}"); 60 | ``` 61 | 62 | *Exercise:* Complete the implementation by overloading the other operators (subtraction, mulitplication, division). Subtraction is very similar to addition, but you' might need more info on complex numbers from the [interactive maths site](http://www.intmath.com/complex-numbers/2-basic-operations.php) to complete the others! 63 | 64 | #### Example 2 65 | 66 | This example shows how operator overloading can be used to implement a three-valued logical type. The possible values of this type are `DBBool.dbTrue`, `DBBool.dbFalse`, and `DBBool.dbNull`, where the `dbNull` member indicates an unknown value. 67 | 68 | > **Note** Defining the True and False operators is only useful for types that represent True, False, and Null (neither True nor False), as used in databases. 69 | 70 | ```csharp 71 | public struct DBBool 72 | { 73 | // The three possible DBBool values: 74 | public static readonly DBBool dbNull = new DBBool(0); 75 | public static readonly DBBool dbFalse = new DBBool(-1); 76 | public static readonly DBBool dbTrue = new DBBool(1); 77 | // Private field that stores -1, 0, 1 for dbFalse, dbNull, dbTrue: 78 | int value; 79 | 80 | // Private constructor. The value parameter must be -1, 0, or 1: 81 | DBBool(int value) 82 | { 83 | this.value = value; 84 | } 85 | 86 | // Implicit conversion from bool to DBBool. Maps true to 87 | // DBBool.dbTrue and false to DBBool.dbFalse: 88 | public static implicit operator DBBool(bool x) 89 | { 90 | return x? dbTrue: dbFalse; 91 | } 92 | 93 | // Explicit conversion from DBBool to bool. Throws an 94 | // exception if the given DBBool is dbNull, otherwise returns 95 | // true or false: 96 | public static explicit operator bool(DBBool x) 97 | { 98 | if (x.value == 0) throw new InvalidOperationException(); 99 | return x.value > 0; 100 | } 101 | 102 | // Equality operator. Returns dbNull if either operand is dbNull, 103 | // otherwise returns dbTrue or dbFalse: 104 | public static DBBool operator ==(DBBool x, DBBool y) 105 | { 106 | if (x.value == 0 || y.value == 0) return dbNull; 107 | return x.value == y.value? dbTrue: dbFalse; 108 | } 109 | 110 | // Inequality operator. Returns dbNull if either operand is 111 | // dbNull, otherwise returns dbTrue or dbFalse: 112 | public static DBBool operator !=(DBBool x, DBBool y) 113 | { 114 | if (x.value == 0 || y.value == 0) return dbNull; 115 | return x.value != y.value? dbTrue: dbFalse; 116 | } 117 | 118 | // Logical negation operator. Returns dbTrue if the operand is 119 | // dbFalse, dbNull if the operand is dbNull, or dbFalse if the 120 | // operand is dbTrue: 121 | public static DBBool operator !(DBBool x) 122 | { 123 | return new DBBool(-x.value); 124 | } 125 | 126 | // Logical AND operator. Returns dbFalse if either operand is 127 | // dbFalse, dbNull if either operand is dbNull, otherwise dbTrue: 128 | public static DBBool operator &(DBBool x, DBBool y) 129 | { 130 | return new DBBool(x.value < y.value? x.value: y.value); 131 | } 132 | 133 | // Logical OR operator. Returns dbTrue if either operand is 134 | // dbTrue, dbNull if either operand is dbNull, otherwise dbFalse: 135 | public static DBBool operator |(DBBool x, DBBool y) 136 | { 137 | return new DBBool(x.value > y.value? x.value: y.value); 138 | } 139 | 140 | // Definitely true operator. Returns true if the operand is 141 | // dbTrue, false otherwise: 142 | public static bool operator true(DBBool x) 143 | { 144 | return x.value > 0; 145 | } 146 | 147 | // Definitely false operator. Returns true if the operand is 148 | // dbFalse, false otherwise: 149 | public static bool operator false(DBBool x) 150 | { 151 | return x.value < 0; 152 | } 153 | 154 | // Overload the conversion from DBBool to string: 155 | public static implicit operator string(DBBool x) 156 | { 157 | return x.value > 0 ? "dbTrue" 158 | : x.value < 0 ? "dbFalse" 159 | : "dbNull"; 160 | } 161 | 162 | // Override the Object.Equals(object o) method: 163 | public override bool Equals(object o) 164 | { 165 | try 166 | { 167 | return (bool) (this == (DBBool) o); 168 | } 169 | catch 170 | { 171 | return false; 172 | } 173 | } 174 | 175 | // Override the Object.GetHashCode() method: 176 | public override int GetHashCode() 177 | { 178 | return value; 179 | } 180 | 181 | // Override the ToString method to convert DBBool to a string: 182 | public override string ToString() 183 | { 184 | switch (value) 185 | { 186 | case -1: 187 | return "DBBool.False"; 188 | case 0: 189 | return "DBBool.Null"; 190 | case 1: 191 | return "DBBool.True"; 192 | default: 193 | throw new InvalidOperationException(); 194 | } 195 | } 196 | } 197 | ``` 198 | 199 | Test the overloaded operators: 200 | 201 | ```csharp 202 | DBBool a, b; 203 | a = DBBool.dbTrue; 204 | b = DBBool.dbNull; 205 | 206 | Console.WriteLine( "!{0} = {1}", a, !a); 207 | Console.WriteLine( "!{0} = {1}", b, !b); 208 | Console.WriteLine( "{0} & {1} = {2}", a, b, a & b); 209 | Console.WriteLine( "{0} | {1} = {2}", a, b, a | b); 210 | // Invoke the true operator to determine the Boolean 211 | // value of the DBBool variable: 212 | if (b) 213 | Console.WriteLine("b is definitely true"); 214 | else 215 | Console.WriteLine("b is not definitely true"); 216 | ``` -------------------------------------------------------------------------------- /Csharp6/README.md: -------------------------------------------------------------------------------- 1 | C# 6 (Inspector) 2 | ========= 3 | 4 | A smattering of C# 6 syntax examples from this 5 | [C# 6 overview](https://developer.xamarin.com/guides/cross-platform/advanced/csharp_six/). 6 | 7 | ![](Screenshots/csharp6-inspector.png) 8 | -------------------------------------------------------------------------------- /Csharp6/Screenshots/csharp6-inspector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Csharp6/Screenshots/csharp6-inspector.png -------------------------------------------------------------------------------- /Csharp6/Structs.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platforms: 4 | - Console 5 | --- 6 | 7 | # Structs 8 | 9 | Workbooks conversion of an [MSDN tutorial on Structs](https://msdn.microsoft.com/en-us/library/aa288471(v=vs.71).aspx). 10 | 11 | ## Tutorial 12 | 13 | This tutorial includes two examples. The first example shows you how to declare and use structs, and the second example demonstrates the difference between structs and classes when instances are passed to methods. You are also introduced to the following topics: 14 | 15 | * Structs vs. Classes 16 | 17 | * Heap or Stack? 18 | 19 | * Constructors and Inheritance 20 | 21 | * Attributes on Structs 22 | 23 | #### Example 1 24 | 25 | This example declares a struct with three members: a property, a method, and a private field. It creates an instance of the `struct` and puts it to use: 26 | 27 | ```csharp 28 | struct SimpleStruct 29 | { 30 | private int xval; 31 | public int X 32 | { 33 | get 34 | { 35 | return xval; 36 | } 37 | set 38 | { 39 | if (value < 100) 40 | xval = value; 41 | } 42 | } 43 | public void DisplayX() 44 | { 45 | Console.WriteLine("The stored value is: {0}", xval); 46 | } 47 | } 48 | ``` 49 | 50 | ```csharp 51 | var ss1 = new SimpleStruct(); 52 | ss1.X = 5; 53 | ss1.DisplayX(); 54 | ``` 55 | 56 | Alternatively, set a value that doesn’t pass the `if` statement, and the property will have `int`’s default value: 57 | 58 | ```csharp 59 | var ss2 = new SimpleStruct(); 60 | ss2.X = 101; 61 | ss2.DisplayX(); 62 | ``` 63 | 64 | ### Structs vs. Classes 65 | 66 | Structs may seem similar to classes, but there are important differences that you should be aware of. First of all, classes are **reference types** and structs are **value types**. By using structs, you can create objects that behave like the built-in types and enjoy their benefits as well. 67 | 68 | ### Heap or Stack? 69 | 70 | When you call the `new` operator on a class, it will be allocated on the heap. However, when you instantiate a struct, it gets created on the stack. This will yield performance gains. Also, you will not be dealing with references to an instance of a struct as you would with classes. You will be working directly with the struct instance. Because of this, when passing a struct to a method, it's passed by value instead of as a reference. 71 | 72 | #### Example 2 73 | 74 | This example shows that when a struct is passed to a method, a copy of the struct is passed, but when a class instance is passed, a reference is passed. 75 | 76 | ```csharp 77 | class TheClass 78 | { 79 | public int x; 80 | } 81 | struct TheStruct 82 | { 83 | public int x; 84 | } 85 | void structtaker(TheStruct s) 86 | { 87 | s.x = 5; 88 | } 89 | void classtaker(TheClass c) 90 | { 91 | c.x = 5; 92 | } 93 | 94 | var a = new TheStruct(); 95 | var b = new TheClass(); 96 | a.x = 1; 97 | b.x = 1; 98 | structtaker(a); 99 | classtaker(b); 100 | 101 | Console.WriteLine($"a.x = {a.x}"); 102 | Console.WriteLine($"b.x = {b.x}"); 103 | ``` 104 | 105 | C# *does* provide a way to pass structs by reference, using the `ref` keyword. Both the method declaration and caller require the `ref` keyword to appear before the parameter. 106 | 107 | ```csharp 108 | void structtakerref(ref TheStruct s) 109 | { 110 | s.x = 5; 111 | } 112 | var c = new TheStruct(); 113 | c.x = 1; 114 | structtakerref(ref c); 115 | Console.WriteLine($"c.x = {c.x}"); 116 | ``` 117 | 118 | #### Code Discussion 119 | 120 | The output of the example shows that only the value of the class field was changed when the class instance was passed to the `classtaker` method. The struct field, however, did not change by passing its instance to the `structtaker` method. This is because a copy of the struct was passed to the `structtaker` method, while a reference to the class was passed to the `classtaker` method. 121 | 122 | ### Constructors and Inheritance 123 | 124 | Structs can declare constructors, but they must take parameters. It is an error to declare a default (parameterless) constructor for a struct. Struct members cannot have initializers. A default constructor is always provided to initialize the struct members to their default values. 125 | 126 | When you create a struct object using the `new` operator, it gets created and the appropriate constructor is called. Unlike classes, structs can be instantiated without using the `new` operator. If you do not use `new`, the fields will remain unassigned and the object cannot be used until all the fields are initialized. 127 | 128 | There is no inheritance for structs as there is for classes. A struct cannot inherit from another struct or class, and it cannot be the base of a class. Structs, however, inherit from the base class object. A struct can implement interfaces, and it does that exactly as classes do. Here's a code snippet of a struct implementing an interface: 129 | 130 | `interface IImage 131 | { 132 | void Paint(); 133 | } 134 | 135 | struct Picture : IImage 136 | { 137 | public void Paint() 138 | { 139 | // painting code goes here 140 | } 141 | private int x, y, z; // other struct members 142 | } 143 | ` 144 | 145 | ### Attributes on Structs 146 | 147 | By using attributes you can customize how structs are laid out in memory. For example, you can create what's known as a union in C/C\+\+ by using the **`StructLayout(LayoutKind.Explicit)`** and **`FieldOffset`** attributes. 148 | 149 | ```csharp 150 | using System.Runtime.InteropServices; 151 | [StructLayout(LayoutKind.Explicit)] 152 | struct TestUnion 153 | { 154 | [FieldOffset(0)] 155 | public int i; 156 | [FieldOffset(0)] 157 | public double d; 158 | [FieldOffset(0)] 159 | public char c; 160 | [FieldOffset(0)] 161 | public byte b1; 162 | } 163 | ``` 164 | 165 | In the preceding code segment, all of the fields of `TestUnion` start at the same location in memory. The example below sets the integer field to `65` (the ASCII code for capital A). Expand the results and notice the other fields are set - the char renders as ‘A’ 166 | 167 | ```csharp 168 | var tu = new TestUnion { i = 65}; 169 | ``` 170 | 171 | The following is another example where fields start at different explicitly set locations: 172 | 173 | ```csharp 174 | using System.Runtime.InteropServices; 175 | [StructLayout(LayoutKind.Explicit)] 176 | struct TestExplicit 177 | { 178 | [FieldOffset(0)] 179 | public long lg; 180 | [FieldOffset(0)] 181 | public int i1; 182 | [FieldOffset(4)] 183 | public int i2; 184 | [FieldOffset(8)] 185 | public double d; 186 | [FieldOffset(12)] 187 | public char c; 188 | [FieldOffset(14)] 189 | public byte b1; 190 | } 191 | ``` 192 | 193 | The two 32-bit **int** fields, `i1` and `i2`, share the same memory locations as the 64-bit field `lg`. Setting the 64-bit value, as shown here, means `i1` and `i2` are also set. Because it’s set to a low number, `i2` remains zero and `i1` contains the same value as `lg`. Expand the result below to confirm: 194 | 195 | ```csharp 196 | var te1 = new TestExplicit {lg = 42}; 197 | ``` 198 | 199 | *Exercise:* try setting the `lg` property to a large value to see the effect on the fields (try 0x10100000101, for example). Tip: use the **0x** view of the result’s fields. 200 | 201 | The next example sets the second 32-bit field. Expand the result to see how this affects the 64-bit field: 202 | 203 | ```csharp 204 | var te2 = new TestExplicit {i2 = 1}; 205 | ``` 206 | 207 | Switch the `lg` rendering to **0x**, and you’ll see `0x100000000` – the eight zeros are the contents of `i1` and the 1 is the value if `i2`. 208 | 209 | This sort of control over struct layout is useful when using platform invocation. 210 | 211 | ### Conclusion 212 | 213 | Structs are simple to use and can prove to be useful at times. Just keep in mind that they're created on the stack and that you're not dealing with references to them but dealing directly with them. Whenever you have a need for a type that will be used often and is mostly just a piece of data, structs might be a good option. -------------------------------------------------------------------------------- /Csharp6/csharp6.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platforms: 4 | - Console 5 | --- 6 | 7 | # Using C# 6 8 | 9 | Some examples from Xamarin's [intro to C# 6](https://developer.xamarin.com/guides/cross-platform/advanced/csharp_six/). 10 | 11 | * Null-conditional operator 12 | 13 | * String Interpolation 14 | 15 | * Expression-bodied Function Members 16 | 17 | * Auto-property Initialization 18 | 19 | * Index Initializers 20 | 21 | * using static 22 | 23 | ## Null-conditional operator 24 | 25 | The `?.` operator automatically does a null-check before referencing the 26 | specified member. The example string array below has a `null` entry: 27 | 28 | ```csharp 29 | var names = new string[] { "Foo", null }; 30 | ``` 31 | 32 | In C# 5, a null-check is required before accessing the `.Length` property: 33 | 34 | ```csharp 35 | // C# 5 36 | int secondLength = 0; 37 | if (names[1] != null) 38 | secondLength = names[1].Length; 39 | ``` 40 | 41 | C# 6 allows the length to be queried in a single line; the entire 42 | statement returns `null` if any object is null. 43 | 44 | ```csharp 45 | var length0 = names[0]?.Length; // 3 46 | var length1 = names[1]?.Length; // null 47 | ``` 48 | 49 | This can be used in conjunction with the `??` null coalescing operator 50 | to set a default value (such as `0`) in the example below: 51 | 52 | ```csharp 53 | var lengths = names.Select (names => names?.Length ?? 0); //[3, 0] 54 | ``` 55 | 56 | ## String Interpolation 57 | 58 | Previously strings were built in a number of different ways: 59 | 60 | ```csharp 61 | var animal = "Monkeys"; 62 | var food = "bananas"; 63 | 64 | var out1 = String.Format ("{0} love to eat {1}", animal, food); 65 | var out2 = animal + " love to eat " + food; 66 | // or even StringBuilder 67 | ``` 68 | 69 | C# 6 provides a simple syntax where the fieldname can be 70 | embedded directly in the string: 71 | 72 | ```csharp 73 | $"{animal} love to eat {food}" 74 | ``` 75 | 76 | String-formatting can also be done with this syntax: 77 | 78 | ```csharp 79 | var values = new int[] { 1, 2, 3, 4, 12, 123456 }; 80 | foreach (var s in values.Select (i => $"The value is {i,10:N2}.")) { 81 | Console.WriteLine (s); 82 | } 83 | ``` 84 | 85 | ## Expression-bodied Function Members 86 | 87 | The `ToString` override in the following class is an expression-bodied 88 | function - a more succinct declaration syntax. 89 | 90 | ```csharp 91 | class Person 92 | { 93 | public string FirstName { get; } 94 | public string LastName { get; } 95 | public Person (string firstname, string lastname) 96 | { 97 | FirstName = firstname; 98 | LastName = lastname; 99 | } 100 | // note there is no explicit `return` keyword 101 | public override string ToString () => $"{LastName}, {FirstName} {LastName}"; 102 | } 103 | ``` 104 | 105 | `void` expression bodied functions are also allowed so long as 106 | the expression is a statement: 107 | 108 | ```csharp 109 | public void Log(string message) => System.Console.WriteLine($"{DateTime.Now.ToString ("s", System.Globalization.CultureInfo.InvariantCulture )}: {message}"); 110 | ``` 111 | 112 | This simple example calls these two methods: 113 | 114 | ```csharp 115 | Log(new Person("James", "Bond").ToString()) 116 | ``` 117 | 118 | ## Auto-property Initialization 119 | 120 | Properties (ie. specified with `{get;set;}`) can be initialized inline 121 | with C# 6: 122 | 123 | ```csharp 124 | class Todo 125 | { 126 | public bool Done { get; set; } = false; 127 | public DateTime Created { get; } = DateTime.Now; 128 | public string Description { get; } 129 | 130 | public Todo (string description) 131 | { 132 | this.Description = description; // can assign (only in constructor!) 133 | } 134 | public override string ToString () => $"'{Description}' was created on {Created}"; 135 | } 136 | ``` 137 | 138 | ```csharp 139 | new Todo("buy apples") 140 | ``` 141 | 142 | ## Index Initializers 143 | 144 | Dictionary-style data structures let you specify key/value 145 | types with a simple object-initializer-like syntax: 146 | 147 | ```csharp 148 | var userInfo = new Dictionary { 149 | ["Created"] = DateTime.Now, 150 | ["Due"] = DateTime.Now.AddSeconds(60 * 60 * 24), 151 | ["Task"] = "buy lettuce" 152 | }; 153 | ``` 154 | 155 | ## using static 156 | 157 | Enumerations, and certain classes such as System.Math, are primarily 158 | holders of static values and functions. In C# 6, you can import all 159 | static members of a type with a single using static statement: 160 | 161 | ```csharp 162 | using static System.Math; 163 | ``` 164 | 165 | C# 6 code can then reference the static members directly, avoiding 166 | repetition of the class name (eg. `Math.PI` becomes `PI`): 167 | 168 | ```csharp 169 | public class Location 170 | { 171 | public Location (double lat, double @long) {Latitude = lat; Longitude = @long;} 172 | public double Latitude = 0; public double Longitude = 0; 173 | } 174 | static public double MilesBetween(Location loc1, Location loc2) 175 | { 176 | double rlat1 = PI * loc1.Latitude / 180; 177 | double rlat2 = PI * loc2.Latitude / 180; 178 | double theta = loc1.Longitude - loc2.Longitude; 179 | double rtheta = PI * theta / 180; 180 | double dist = 181 | Sin(rlat1) * Sin(rlat2) + Cos(rlat1) * 182 | Cos(rlat2) * Cos(rtheta); 183 | dist = Acos(dist); 184 | dist = dist*180/PI; 185 | dist = dist*60*1.1515; 186 | return dist; //miles 187 | } 188 | ``` 189 | 190 | ```csharp 191 | MilesBetween (new Location(-12,22), new Location(-13,33)) 192 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MagicEightBall/README.md: -------------------------------------------------------------------------------- 1 | Magic Eight Ball (Inspector) 2 | ========= 3 | 4 | A quick interactive sample of this [Xamarin.Forms version](https://github.com/conceptdev/xamarin-forms-samples/tree/master/MagicEightBall) 5 | of the classic **Magic Eight Ball** 6 | 7 | ![](Screenshots/8ball-inspector.png) 8 | -------------------------------------------------------------------------------- /MagicEightBall/Screenshots/8ball-inspector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/MagicEightBall/Screenshots/8ball-inspector.png -------------------------------------------------------------------------------- /MagicEightBall/magic8ball.workbook: -------------------------------------------------------------------------------- 1 | ```json 2 | {"platform":"MacNet34","uti":"com.xamarin.Workbook"} 3 | ``` 4 | # Magic Eight Ball 🎱 5 | 6 | 1. Create a list of options for the magic eight ball 7 | 8 | ```csharp 9 | string[] options = { 10 | "It is certain", "It is decidedly so", "Without a doubt", "Yes definitely", "You may rely on it", "As I see it, yes", "Most likely", "Outlook good", "Yes", "Signs point to yes" 11 | , "Reply hazy try again", "Ask again later", "Better not tell you now", "Cannot predict now", "Concentrate and ask again" 12 | , "Don't count on it", "My reply is no", "My sources say no", "Outlook not so good", "Very doubtful" 13 | }; 14 | 15 | ``` 16 | 17 | 2. Use a random number to pick a response 18 | 19 | ```csharp 20 | string Shake () { 21 | var rnd = new System.Random(); 22 | return options[rnd.Next(0, options.Length - 1)]; 23 | } 24 | ``` 25 | 26 | 3. Now `Shake()` 27 | 28 | ```csharp 29 | Shake() 30 | ``` 31 | -------------------------------------------------------------------------------- /Mastermind/README.md: -------------------------------------------------------------------------------- 1 | Mastermind (Inspector) 2 | ========= 3 | 4 | A quick interactive version of the classic **Mastermind** guessing game! 5 | 6 | ## Play 7 | 8 | ![](Screenshots/mastermind-play.png) 9 | 10 | ## Code 11 | 12 | ![](Screenshots/mastermind-inspector.png) 13 | 14 | 15 | -------------------------------------------------------------------------------- /Mastermind/Screenshots/mastermind-inspector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Mastermind/Screenshots/mastermind-inspector.png -------------------------------------------------------------------------------- /Mastermind/Screenshots/mastermind-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Mastermind/Screenshots/mastermind-play.png -------------------------------------------------------------------------------- /Mastermind/mastermind.workbook: -------------------------------------------------------------------------------- 1 | ```json 2 | {"platform":"MacNet45","uti":"com.xamarin.Workbook"} 3 | ``` 4 | 5 | # Mastermind ⚫️⚪️🔴 6 | 7 | First, set up an array of four random numbers 1-4: 8 | 9 | ```csharp 10 | int[] game = null; 11 | 12 | var rnd = new System.Random(); 13 | int guess () { 14 | return rnd.Next(0, 3) + 1; 15 | } 16 | int[] board () { 17 | return new int[] { 18 | guess(), guess(), guess(), guess() 19 | }; 20 | } 21 | ``` 22 | 23 | Now write an algorithm to check guesses against the random 24 | numbers in the array. 25 | 26 | ```csharp 27 | NSColor[] play (params int[] attempt){ 28 | if (game == null) game = board(); 29 | int blacks=0, whites=0, reds=0; 30 | var incorrect = new bool[4]; 31 | // blacks 32 | for (var i = 0; i<4; i++){ 33 | if (attempt[i] == game[i]) 34 | blacks++; 35 | else 36 | incorrect[i] = true; 37 | } 38 | // whites and reds (buggy) 39 | for (var j = 0; j<4; j++){ 40 | if (incorrect[j]) { 41 | for (var k = 0; k<4; k++){ 42 | if (j == k) {/*nothing*/} 43 | else 44 | if (incorrect[k]) { 45 | if (attempt[j] == game[k]) { 46 | whites++; attempt[j] = -1; 47 | } 48 | } 49 | } 50 | } 51 | } 52 | reds = 4 - whites - blacks; 53 | 54 | var result = new List(); 55 | while (blacks > 0) {result.Add(NSColor.Black); blacks--;} 56 | while (whites > 0) {result.Add(NSColor.White); whites--;} 57 | while (reds > 0) {result.Add(NSColor.Red); reds--;} 58 | return result.ToArray(); 59 | } 60 | ``` 61 | 62 | # To Play 63 | 64 | Enter your guesses like this `play (1,2,3,4)` (each number 65 | is between 1 and 4). 66 | 67 | * Black ⚫️ – Correct number and position 68 | * White ⚪️ – Correct number, incorrect position (buggy) 69 | * Red 🔴– Partly incorrect guess 70 | -------------------------------------------------------------------------------- /MobileCenter/README.md: -------------------------------------------------------------------------------- 1 | Mobile Center API 2 | ========= 3 | 4 | [Xamarin Workbooks](https://developer.xamarin.com/workbooks/) 5 | demo for the [Mobile Center API](https://docs.mobile.azure.com/api/) (currently in preview). 6 | 7 | *Requires a Mobile Center email login (not Github auth)* 8 | 9 | ![](Screenshots/sonoma.png) 10 | -------------------------------------------------------------------------------- /MobileCenter/Screenshots/sonoma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/MobileCenter/Screenshots/sonoma.png -------------------------------------------------------------------------------- /MobileCenter/Sonoma-httpclient.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platforms: 4 | - iOS 5 | - Console 6 | --- 7 | 8 | # MobileCenter API (HttpClient) 9 | 10 | Check out the [Mobile Center API Swagger](https://docs.mobile.azure.com/api/) to see what you can do! 11 | To start with, enter your login credentials (NOT your Github credentials, they won't work here): 12 | 13 | ```csharp 14 | var username = "EMAIL_ADDRESS"; 15 | var password = "PASSWORD"; 16 | ``` 17 | 18 | ## Authorize 19 | 20 | Before using the API, your login credentials are required to obtain an API token 21 | that will be used to authorize subsequent requests. 22 | 23 | ```csharp 24 | using System.Text; 25 | string authInfo = username + ":" + password; 26 | authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo)); 27 | ``` 28 | 29 | ```csharp 30 | #r "System.Net.Http" 31 | using System.Net; 32 | using System.Net.Http; 33 | using System.Net.Http.Headers; 34 | using System.Text; 35 | using System.IO; 36 | 37 | string response = ""; 38 | using (var client = new HttpClient()) 39 | { 40 | client.BaseAddress = new Uri("https://api.mobile.azure.com"); 41 | var content = new StringContent("{}", Encoding.UTF8, "application/json"); // CONTENT-TYPE 42 | client.DefaultRequestHeaders 43 | .Accept 44 | .Add(new MediaTypeWithQualityHeaderValue("application/json"));//ACCEPT header 45 | client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authInfo); 46 | 47 | var result = await client.PostAsync("/v0.1/api_tokens", content); 48 | response = await result.Content.ReadAsStringAsync(); 49 | } 50 | response 51 | ``` 52 | 53 | Create a class (using the Swagger Json documentation as a guide) to deserialize the response: 54 | 55 | ```csharp 56 | public class ApiTokenResponse 57 | { 58 | public string id {get;set;} 59 | public string created_at {get;set;} 60 | public string api_token {get;set;} 61 | } 62 | ``` 63 | 64 | Now deserialize the response into that class 65 | 66 | ```csharp 67 | #r "Newtonsoft.Json" 68 | using Newtonsoft.Json; 69 | 70 | var token = JsonConvert.DeserializeObject (response); 71 | token 72 | ``` 73 | 74 | Woohoo! Now we can use the `api_token` in subsequent requests to the API. 75 | 76 | ## User Profile 77 | 78 | Once again using Swagger as a guide, create a class to populate with API data: 79 | 80 | ```csharp 81 | public class UserProfileResponse 82 | { 83 | public string id {get;set;} 84 | public string email {get;set;} 85 | public string display_name {get;set;} 86 | public string name {get;set;} 87 | public string avatar_url {get;set;} 88 | public bool can_change_password {get;set;} 89 | } 90 | ``` 91 | 92 | then using the API Token in the `X-API-Token` header, make a request to retrieve the user profile 93 | 94 | ```csharp 95 | string response = ""; 96 | using (var client = new HttpClient()) 97 | { 98 | client.BaseAddress = new Uri("https://api.mobile.azure.com"); 99 | client.DefaultRequestHeaders 100 | .Accept 101 | .Add(new MediaTypeWithQualityHeaderValue("application/json"));//ACCEPT header 102 | client.DefaultRequestHeaders.Add("X-API-Token", token.api_token); 103 | 104 | var result = await client.GetAsync("/v0.1/user"); 105 | response = await result.Content.ReadAsStringAsync(); 106 | } 107 | 108 | var profile = JsonConvert.DeserializeObject (response); 109 | profile 110 | ``` 111 | 112 | ## Apps List 113 | 114 | Once again using Swagger as a guide, create a class to populate with API data: 115 | 116 | ```csharp 117 | public class AppResponse 118 | { 119 | public string id {get;set;} 120 | public string app_secret {get;set;} 121 | public string description {get;set;} 122 | public string display_name {get;set;} 123 | public string name {get;set;} 124 | public string platform {get;set;} 125 | public string language {get;set;} 126 | public string icon_url {get;set;} 127 | // ignoring owner info for now 128 | public string azure_subscription_id {get;set;} 129 | } 130 | ``` 131 | 132 | then using the API Token in the `X-API-Token` header, make a request to retrieve the app list 133 | 134 | ```csharp 135 | using (var client = new HttpClient()) 136 | { 137 | client.BaseAddress = new Uri("https://api.mobile.azure.com"); 138 | client.DefaultRequestHeaders 139 | .Accept 140 | .Add(new MediaTypeWithQualityHeaderValue("application/json"));//ACCEPT header 141 | client.DefaultRequestHeaders.Add("X-API-Token", token.api_token); 142 | 143 | var result = await client.GetAsync("/v0.1/apps"); 144 | response = await result.Content.ReadAsStringAsync(); 145 | } 146 | 147 | var apps = JsonConvert.DeserializeObject (response); 148 | apps 149 | ``` 150 | 151 | ```csharp 152 | apps[0] 153 | ``` 154 | 155 | ## Next steps… 156 | 157 | Using variations on the above code you can easily build up interactions with the Mobile Center API, 158 | including reporting or managing apps and services. Check out the [Mobile Center API Swagger](https://docs.mobile.azure.com/api/) for the complete list of operations available. 159 | -------------------------------------------------------------------------------- /MobileCenter/Sonoma-webclient.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platforms: 4 | - iOS 5 | - Console 6 | packages: 7 | - id: Newtonsoft.Json 8 | version: 8.0.3 9 | --- 10 | 11 | # Mobile Center API (WebClient) 12 | 13 | Check out the [Mobile Center API Swagger](https://docs.mobile.azure.com/api/) to see what you can do! 14 | To start with, enter your login credentials (NOT your Github credentials, they won't work here): 15 | 16 | ```csharp 17 | var username = "EMAIL_ADDRESS"; 18 | var password = "PASSWORD"; 19 | ``` 20 | 21 | ## Authorize 22 | 23 | Before using the API, your login credentials are required to obtain an API token 24 | that will be used to authorize subsequent requests. 25 | 26 | ```csharp 27 | using System.Text; 28 | string authInfo = username + ":" + password; 29 | authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo)); 30 | ``` 31 | 32 | ```csharp 33 | using System.Net; 34 | using System.Text; 35 | using System.IO; 36 | 37 | WebClient client = new WebClient(); 38 | client.Headers.Add (HttpRequestHeader.Accept, "application/json"); 39 | client.Headers.Add (HttpRequestHeader.ContentType, "application/json"); 40 | client.Headers.Add ("Authorization", "Basic " + authInfo); 41 | var response = client.UploadString (new Uri("https://api.mobile.azure.com/v0.1/api_tokens"), "POST", "{}"); 42 | ``` 43 | 44 | Create a class (using the Swagger Json documentation as a guide) to deserialize the response: 45 | 46 | ```csharp 47 | public class ApiTokenResponse 48 | { 49 | public string id {get;set;} 50 | public string created_at {get;set;} 51 | public string api_token {get;set;} 52 | } 53 | ``` 54 | 55 | Now deserialize the response into that class 56 | 57 | ```csharp 58 | #r "Newtonsoft.Json" 59 | using Newtonsoft.Json; 60 | 61 | var token = JsonConvert.DeserializeObject (response); 62 | token 63 | ``` 64 | 65 | Woohoo! Now we can use the `api_token` in subsequent requests to the API. 66 | 67 | ## User Profile 68 | 69 | Once again using Swagger as a guide, create a class to populate with API data: 70 | 71 | ```csharp 72 | public class UserProfileResponse 73 | { 74 | public string id {get;set;} 75 | public string email {get;set;} 76 | public string display_name {get;set;} 77 | public string name {get;set;} 78 | public string avatar_url {get;set;} 79 | public bool can_change_password {get;set;} 80 | } 81 | ``` 82 | 83 | then using the API Token in the `X-API-Token` header, make a request to retrieve the user profile 84 | 85 | ```csharp 86 | WebClient client = new WebClient(); 87 | client.Headers.Add (HttpRequestHeader.ContentType, "application/json"); 88 | client.Headers.Add (HttpRequestHeader.Accept, "application/json"); 89 | client.Headers.Add ("X-API-Token", token.api_token); 90 | var response = client.DownloadData (new Uri("https://api.mobile.azure.com/v0.1/user")); 91 | 92 | var responseString = System.Text.Encoding.UTF8.GetString(response); 93 | var profile = JsonConvert.DeserializeObject (responseString); 94 | 95 | profile 96 | ``` 97 | 98 | ## Apps List 99 | 100 | Once again using Swagger as a guide, create a class to populate with API data: 101 | 102 | ```csharp 103 | public class AppResponse 104 | { 105 | public string id {get;set;} 106 | public string app_secret {get;set;} 107 | public string description {get;set;} 108 | public string display_name {get;set;} 109 | public string name {get;set;} 110 | public string platform {get;set;} 111 | public string language {get;set;} 112 | public string icon_url {get;set;} 113 | // ignoring owner info for now 114 | public string azure_subscription_id {get;set;} 115 | } 116 | public class AppsResponse 117 | { 118 | public AppResponse[] Apps {get;set;} 119 | } 120 | ``` 121 | 122 | then using the API Token in the `X-API-Token` header, make a request to retrieve the app list 123 | 124 | ```csharp 125 | WebClient client = new WebClient(); 126 | client.Headers.Add (HttpRequestHeader.ContentType, "application/json"); 127 | client.Headers.Add (HttpRequestHeader.Accept, "application/json"); 128 | client.Headers.Add ("X-API-Token", token.api_token); 129 | var response = client.DownloadData (new Uri("https://api.mobile.azure.com/v0.1/apps")); 130 | 131 | var responseString = System.Text.Encoding.UTF8.GetString(response); 132 | var apps = JsonConvert.DeserializeObject (responseString); 133 | 134 | apps 135 | ``` 136 | 137 | ```csharp 138 | apps[0] 139 | ``` 140 | 141 | ## Next steps… 142 | 143 | Using variations on the above code you can easily build up interactions with the Mobile Center API, 144 | including reporting or managing apps and services. Check out the [Mobile Center API Swagger](https://docs.mobile.azure.com/api/) for the complete list of operations available. 145 | -------------------------------------------------------------------------------- /MobileCenter/Sonoma-webrequest.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platforms: 4 | - iOS 5 | - Console 6 | packages: 7 | - id: Newtonsoft.Json 8 | version: 8.0.3 9 | --- 10 | 11 | # Mobile Center API (WebRequest) 12 | 13 | Check out the [Mobile Center API Swagger](https://docs.mobile.azure.com/api/) to see what you can do! 14 | To start with, enter your login credentials (NOT your Github credentials, they won't work here): 15 | 16 | ```csharp 17 | var username = "EMAIL_ADDRESS"; 18 | var password = "PASSWORD"; 19 | ``` 20 | 21 | ## Authorize 22 | 23 | Before using the API, your login credentials are required to obtain an API token 24 | that will be used to authorize subsequent requests. 25 | 26 | ```csharp 27 | using System.Text; 28 | string authInfo = username + ":" + password; 29 | authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo)); 30 | ``` 31 | 32 | ```csharp 33 | using System.Net; 34 | using System.Text; 35 | using System.IO; 36 | 37 | WebRequest request = WebRequest.Create("https://api.mobile.azure.com/v0.1/api_tokens"); 38 | request.Headers["Authorization"] = "Basic " + authInfo; 39 | request.Method = "POST"; 40 | request.ContentType="application/json"; 41 | 42 | // fill in other request properties here, like content 43 | ASCIIEncoding encoding=new ASCIIEncoding(); 44 | string stringData = "{}"; //place body here 45 | byte[] data = encoding.GetBytes(stringData); 46 | Stream newStream = request.GetRequestStream(); 47 | newStream.Write(data,0,data.Length); 48 | newStream.Close(); 49 | 50 | WebResponse response = request.GetResponse(); 51 | ``` 52 | 53 | Create a class (using the Swagger Json documentation as a guide) to deserialize the response: 54 | 55 | ```csharp 56 | public class ApiTokenResponse 57 | { 58 | public string id {get;set;} 59 | public string created_at {get;set;} 60 | public string api_token {get;set;} 61 | } 62 | ``` 63 | 64 | Now deserialize the response into that class 65 | 66 | ```csharp 67 | #r "Newtonsoft.Json" 68 | using Newtonsoft.Json; 69 | ApiTokenResponse token = null; 70 | using (var stream = response.GetResponseStream()) 71 | using (var reader = new StreamReader (stream)) 72 | using (var jsonReader = new JsonTextReader(reader)) 73 | { 74 | var serializer = new JsonSerializer(); 75 | token = serializer.Deserialize (jsonReader); 76 | } 77 | token 78 | ``` 79 | 80 | Woohoo! Now we can use the `api_token` in subsequent requests to the API. 81 | 82 | ## User Profile 83 | 84 | Once again using Swagger as a guide, create a class to populate with API data: 85 | 86 | ```csharp 87 | public class UserProfileResponse 88 | { 89 | public string id {get;set;} 90 | public string email {get;set;} 91 | public string display_name {get;set;} 92 | public string name {get;set;} 93 | public string avatar_url {get;set;} 94 | public bool can_change_password {get;set;} 95 | } 96 | ``` 97 | 98 | then using the API Token in the `X-API-Token` header, make a request to retrieve the user profile 99 | 100 | ```WebRequest request = WebRequest.Create("https://api.mobile.azure.com/v0.1/user"); 101 | request = WebRequest.Create("https://api.mobile.azure.com/v0.1/user"); 102 | request.Headers["X-API-Token"] = token.api_token; 103 | request.Method = "GET"; 104 | request.ContentType="application/json"; 105 | 106 | response = request.GetResponse(); 107 | UserProfileResponse profile = null; 108 | using (var stream = response.GetResponseStream()) 109 | using (var reader = new StreamReader (stream)) 110 | using (var jsonReader = new JsonTextReader(reader)) 111 | { 112 | var serializer = new JsonSerializer(); 113 | profile = serializer.Deserialize (jsonReader); 114 | } 115 | profile 116 | ``` 117 | 118 | ## Apps List 119 | 120 | Once again using Swagger as a guide, create a class to populate with API data: 121 | 122 | ```csharp 123 | public class AppResponse 124 | { 125 | public string id {get;set;} 126 | public string app_secret {get;set;} 127 | public string description {get;set;} 128 | public string display_name {get;set;} 129 | public string name {get;set;} 130 | public string platform {get;set;} 131 | public string language {get;set;} 132 | public string icon_url {get;set;} 133 | // ignoring owner info for now 134 | public string azure_subscription_id {get;set;} 135 | } 136 | public class AppsResponse 137 | { 138 | public AppResponse[] Apps {get;set;} 139 | } 140 | ``` 141 | 142 | then using the API Token in the `X-API-Token` header, make a request to retrieve the app list 143 | 144 | ```WebRequest request = WebRequest.Create("https://api.mobile.azure.com/v0.1/user"); 145 | request = WebRequest.Create("https://api.mobile.azure.com/v0.1/apps"); 146 | request.Headers["X-API-Token"] = token.api_token; 147 | request.Method = "GET"; 148 | request.ContentType="application/json"; 149 | 150 | response = request.GetResponse(); 151 | AppResponse[] apps = null; 152 | using (var stream = response.GetResponseStream()) 153 | using (var reader = new StreamReader (stream)) 154 | using (var jsonReader = new JsonTextReader(reader)) 155 | { 156 | var serializer = new JsonSerializer(); 157 | apps = serializer.Deserialize (jsonReader); 158 | } 159 | apps 160 | ``` 161 | 162 | ```csharp 163 | apps[0] 164 | ``` 165 | 166 | ## Next steps… 167 | 168 | Using variations on the above code you can easily build up interactions with the Mobile Center API, 169 | including reporting or managing apps and services. Check out the [Mobile Center API Swagger](https://docs.mobile.azure.com/api/) for the complete list of operations available. 170 | -------------------------------------------------------------------------------- /Nuget/JsonNET1.workbook: -------------------------------------------------------------------------------- 1 | ```json 2 | {"exec-mode":"default","platform":"MacNet45","uti":"com.xamarin.workbook","packages":[{"id":"Newtonsoft.Json","version":"8.0.3"},{"id":"Xamarin.Forms","version":"2.2.0.31"}]} 3 | ``` 4 | 5 | # Add a Nuget to a Workbook 6 | 7 | This workbook shows how to include and use a Nuget package from a workbook. The demo uses Json.NET to serialize and de-serialize a C# class. 8 | 9 | * Add the Json.NET Nuget 10 | 11 | * Create the C# object 12 | 13 | * Serialize & Deserialize using Json.NET 14 | 15 | ...borrowing some JSON from James Montemagno’s [sample](https://github.com/jamesmontemagno/MonkeysApp-AppIndexing "Monkey Sample App"). 16 | 17 | ## Add Json.NET 18 | 19 | 1) Start by adding a Nugets — choose **File > Add Package...** to choose Nuget packages to reference. 20 | 21 | ⚠️ *No need to follow these instructions - the package is already referenced by this workbook* 22 | 23 | ![](Screenshots/add-package-sml.png) 24 | 25 | and then 26 | 27 | ![](Screenshots/add-newtonsoft-sml.png) 28 | 29 | For this workbook, **Json.NET** was chosen, which results in the #r clause being added automatically: 30 | 31 | ```csharp 32 | #r "Newtonsoft.Json" 33 | ``` 34 | 35 | Check the workbook’s source to see how this is persisted (if you’re curious): 36 | 37 | `{"platform":"iOS","uti":"com.xamarin.workbook","packages":[{"id":"Newtonsoft.Json","version":"8.0.3"}]}` 38 | 39 | 2) To write some code using the Nuget package, first add a `using` statement: 40 | 41 | ```csharp 42 | using Newtonsoft.Json; 43 | ``` 44 | 45 | ## Create C# object 46 | 47 | 3) Now setup a C# class and some test data... 48 | 49 | ```csharp 50 | public class Monkey 51 | { 52 | public string Name { get; set; } 53 | public string Location { get; set; } 54 | public string Details { get; set; } 55 | public string Image { get; set; } 56 | public int Population { get; set; } 57 | } 58 | var monkey1 = new Monkey{Name="Blue Monkey", Location="Central and East Africa"}; 59 | var monkey2 = new Monkey{Name="Golden Lion Tamarin", Location="Brazil"}; 60 | var monkeyList = new List {monkey1, monkey2}; 61 | ``` 62 | 63 | ## Serialize to JSON 64 | 65 | 4) Using the `JsonConvert` class from the Nuget package, the C# data can be converted to JSON with a single line of code! 66 | 67 | ```csharp 68 | var toJson = JsonConvert.SerializeObject(monkeyList); 69 | ``` 70 | 71 | ## Deserialize from JSON 72 | 73 | 5) To test deserialization, here is a more complete JSON string (thanks to James Montemagno’s sample [here](https://raw.githubusercontent.com/jamesmontemagno/MonkeysApp-AppIndexing/master/MonkeysApp/monkeydata.json)): 74 | 75 | ```csharp 76 | var fromJson = @"[{""Name"":""Baboon"",""Location"":""Africa & Asia"",""Details"":""Baboons are African and Arabian Old World monkeys belonging to the genus Papio, part of the subfamily Cercopithecinae."",""Image"":""http://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Portrait_Of_A_Baboon.jpg/314px-Portrait_Of_A_Baboon.jpg"",""Population"":10000}, 77 | {""Name"":""Capuchin Monkey"",""Location"":""Central & South America"",""Details"":""The capuchin monkeys are New World monkeys of the subfamily Cebinae. Prior to 2011, the subfamily contained only a single genus, Cebus."",""Image"":""http://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Capuchin_Costa_Rica.jpg/200px-Capuchin_Costa_Rica.jpg"",""Population"":23000}]"; 78 | ``` 79 | 80 | 6) Converting a JSON string back to C# objects is also a one-liner - and results in a C# collection that can be utilized in app code: 81 | 82 | ```csharp 83 | var monkeys = JsonConvert.DeserializeObject>(fromJson); 84 | ``` 85 | 86 | The example above uses a JSON string, but the data could also be loaded from a local file or an internet download (either a file reference or REST web service endpoint). 87 | 88 | ## One More Thing... 89 | 90 | To load the JSON string from a remote website, just add `System.Net` to the workbook 91 | 92 | ```csharp 93 | using System.Net; 94 | ``` 95 | 96 | and then use WebClient to download it before serialization 97 | 98 | ```csharp 99 | WebClient client = new WebClient(); 100 | var response = client.DownloadData ("https://raw.githubusercontent.com/jamesmontemagno/MonkeysApp-AppIndexing/master/MonkeysApp/monkeydata.json"); // GET 101 | var json = System.Text.Encoding.UTF8.GetString(response); 102 | var monkeys = JsonConvert.DeserializeObject>(json); 103 | ``` 104 | 105 | -------------------------------------------------------------------------------- /Nuget/Screenshots/JsonNET1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Nuget/Screenshots/JsonNET1.png -------------------------------------------------------------------------------- /Nuget/Screenshots/add-newtonsoft-sml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Nuget/Screenshots/add-newtonsoft-sml.png -------------------------------------------------------------------------------- /Nuget/Screenshots/add-newtonsoft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Nuget/Screenshots/add-newtonsoft.png -------------------------------------------------------------------------------- /Nuget/Screenshots/add-package-sml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Nuget/Screenshots/add-package-sml.png -------------------------------------------------------------------------------- /Nuget/Screenshots/add-package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Nuget/Screenshots/add-package.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Xamarin Workbook Samples 2 | ========= 3 | 4 | Samples for the [Xamarin Workbooks](https://developer.xamarin.com/guides/cross-platform/workbooks/) 5 | interactive window, including: 6 | 7 | ## Magic 8 Ball 8 | 9 | ![](Screenshots/8ball-inspector-sml.png) 10 | 11 | ## Mastermind 12 | 13 | ![](Screenshots/mastermind-inspector-sml.png) 14 | 15 | ## C# 6 features 16 | 17 | ![](Screenshots/csharp6-inspector-sml.png) 18 | -------------------------------------------------------------------------------- /Screenshots/8ball-inspector-sml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Screenshots/8ball-inspector-sml.png -------------------------------------------------------------------------------- /Screenshots/csharp6-inspector-sml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Screenshots/csharp6-inspector-sml.png -------------------------------------------------------------------------------- /Screenshots/mastermind-inspector-sml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Screenshots/mastermind-inspector-sml.png -------------------------------------------------------------------------------- /ScrollingLists/actionsource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/ScrollingLists/actionsource.png -------------------------------------------------------------------------------- /ScrollingLists/customsource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/ScrollingLists/customsource.png -------------------------------------------------------------------------------- /ScrollingLists/detailsource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/ScrollingLists/detailsource.png -------------------------------------------------------------------------------- /ScrollingLists/editsource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/ScrollingLists/editsource.png -------------------------------------------------------------------------------- /ScrollingLists/imagesource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/ScrollingLists/imagesource.png -------------------------------------------------------------------------------- /ScrollingLists/ios-tableview1.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platform: iOS 4 | packages: [] 5 | --- 6 | 7 | # UITableView I 8 | 9 | One of the most common controls in iOS is the table view - it renders a fast scrolling list of data and can be heavily customized. This workbook demonstrates how to display data in a table view, and is based on the [Tables and Cells section](https://developer.xamarin.com/guides/ios/user_interface/tables/) on [developer.xamarin.com](https://developer.xamarin.com). 10 | 11 | * Setup 12 | 13 | * Showing Some Data 14 | 15 | * Animated Insertion & Deletion 16 | 17 | * Customizing Each Row 18 | 19 | * Built-in Layouts 20 | 21 | * Custom Cell Layout 22 | 23 | * Row Selection 24 | 25 | * Row Actions 26 | 27 | * Edit Mode 28 | 29 | First, some setup. Grab a reference to the iOS root `ViewController` (in your app code, a table view might be added to any view controller): 30 | 31 | ```csharp 32 | var vc = KeyWindow.RootViewController; 33 | ``` 34 | 35 | ## Setup 36 | 37 | Now instantiate a `UITableView`control. Notice how the offset and size are all zero - by default the table has no size... 38 | 39 | ```csharp 40 | var tableView = new UITableView(); 41 | ``` 42 | 43 | Table views frequently occupy the entire screen, so set the `Frame`for the table to match the `Bounds`of the screen. The tableView will now have a `Width`and `Height`: 44 | 45 | ```csharp 46 | tableView.Frame = UIScreen.MainScreen.Bounds; 47 | ``` 48 | 49 | The table is still not visible on the screen - to make that happen we need to add the control to the root view controller’s `View`, with the `AddSubview`method. When this method is called the table will be visible on the screen (despite having no data, an empty table view displays gridlines based on the default row height): 50 | 51 | ```csharp 52 | vc.View.AddSubview(tableView); 53 | ``` 54 | 55 | In your app code, the AddSubview command is usually in the `ViewDidLoad` method. Alternatively, you can drag-and-drop a `UITableView`control onto a storyboard and give it a name, which means you do not have to do the above steps in code. 56 | 57 | ## Showing Some Data 58 | 59 | Real apps frequently populate the UI from a database or a web-service, but for learning about table views a simple string list is a good start: 60 | 61 | ```csharp 62 | var data = new List {"eeny", "meany", "miney", "mo"}; 63 | ``` 64 | 65 | iOS table views don’t have the concept of a **BindingContext** like Xamarin.Forms, so there is no direct way to “assign” the list to the table view. Instead a wrapper object subclassed from `UITableViewSource` is used - the table view uses this class to get information about the data it needs to display. 66 | 67 | There are many optional methods, but the minimum implementation requires: 68 | 69 | * `RowsInSection` – a method that tells the table view how many rows are in the data. Table views support multiple sections, but for this example there is only one section so the concept can be ignored for now. 70 | 71 | * `GetCell` – the table view calls this method for each row that it needs to display. There are two parts to this method: first get a recycled cell or create a new cell object, then set the UI of the cell with values from the data for that row. 72 | 73 | An example subclass - `MySource` - is shown below. 74 | 75 | ```csharp 76 | public class MySource : UITableViewSource 77 | { 78 | string identifier = "mycell"; 79 | public List Data {get;set;} = new List(); // C# 6 80 | public override nint RowsInSection (UITableView tableview, nint section) 81 | { 82 | return Data.Count; 83 | } 84 | public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath) 85 | { 86 | // first, get or create a cell 87 | UITableViewCell cell = tableView.DequeueReusableCell (identifier); 88 | if (cell == null) 89 | { cell = new UITableViewCell (UITableViewCellStyle.Default, identifier); } 90 | // then, get the data and set the UI 91 | string item = Data[indexPath.Row]; 92 | cell.TextLabel.Text = item; 93 | return cell; 94 | } 95 | } 96 | ``` 97 | 98 | To use the new class, instantiate it and set the `Data` property to the list of strings. Now it’s ready for the table view, so assign it to the table view’s `Source` property. The table is now ready to display the data - call `ReloadData` to see it rendered on the screen: 99 | 100 | ```csharp 101 | var source = new MySource(); // create the class 102 | source.Data = data; // assign the list of strings 103 | tableView.Source = source; // give it to the table view 104 | tableView.ReloadData(); // and show on the screen 105 | ``` 106 | 107 | ![](mysource.png) 108 | 109 | *(ignore the status bar overlapping for now - that is a layout issue for another workbook)* 110 | 111 | What happens when a new item is added to the data list? 112 | 113 | ```csharp 114 | data.Add("d'oh"); 115 | ``` 116 | 117 | Sadly, it does not automatically appear on the screen. We can force the table view to re-load the entire list by calling `ReloadData`: 118 | 119 | ```csharp 120 | tableView.ReloadData(); 121 | ``` 122 | 123 | The new “d’oh” row is now visible - but there may have been a flicker in the UI, since the entire table was cleared and the rows re-created. This is sometimes acceptable for simple apps with small datasets, but there is a nicer way to add (and remove) rows programmatically.... with animation! 124 | 125 | ### Animated Insertion & Deletion 126 | 127 | Rather than reloading the entire table, rows can be progammatically added and removed. To demonstrate, insert two new rows at position 2 and 4 in the list so that they appear with an animation. The first step is to create `NSIndexPath`objects that refer to the new rows we’re inserting: 128 | 129 | ```csharp 130 | var ip1 = NSIndexPath.FromRowSection(2, 0); // row 2 section 0 131 | var ip2 = NSIndexPath.FromRowSection(4, 0); // row 4 section 0 132 | ``` 133 | 134 | Instead of using `ReloadData`to refresh the entire list, it is possible to get the table to dynamically add the new rows with the code below. Notice how the modifications to both the underlying `data` and the `tableView` are contained inside a `BeginUpdates`/`EndUpdates` block: 135 | 136 | ```csharp 137 | tableView.BeginUpdates(); 138 | // update the underlying data source 139 | data.Insert (2, "a deer"); 140 | data.Insert (4, "ray"); 141 | // tell the UITableView about the updates with NSIndexPath objects 142 | tableView.InsertRows(new [] {ip1, ip2}, UITableViewRowAnimation.Automatic); 143 | tableView.EndUpdates(); 144 | ``` 145 | 146 | It is only after `EndUpdates` is called that the changes are reflected in the user interface. All the changes made to the `data` and the `tableView` must match (ie. the number of rows in each must be identical before the block and after the block). 147 | 148 | Notice how the new rows animate into place. In addition to `Automatic` there are other animation options like `Top`, `Bottom`, `Left`, `Right`, `Middle`, `Fade` that can be chosen to suit the application’s user interface. 149 | 150 | The `DeleteRows`method can also be used to remove a row. This is commented out below, since running the workbook over and over will remove all the data! Uncomment and execute this block to see a row removal animation. 151 | 152 | ```csharp 153 | /* 154 | var ip2 = NSIndexPath.FromRowSection(3, 0); 155 | tableView.BeginUpdates(); 156 | data.RemoveAt (3); 157 | tableView.DeleteRows(new [] {ip1, ip2}, UITableViewRowAnimation.Automatic); 158 | tableView.EndUpdates(); 159 | */ 160 | ``` 161 | 162 | ## Customizing Each Row 163 | 164 | Each row that is displayed is supplied to the table view from the source’s `GetCell` method. The code above uses the simplest default cell layout in the `GetCell` method. This method can be further customized to use different built-in or custom cells. 165 | 166 | ### Built-in Layouts 167 | 168 | In the updated source class below, the cell is now `UITableViewCellStyle.Subtitle` (instead of `Default`) and the code sets the additional `cell.DetailTextLabel.Text` property. 169 | 170 | ```csharp 171 | public class DetailSource : MySource 172 | { 173 | string identifier = "newcell"; 174 | public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath) 175 | { 176 | UITableViewCell cell = tableView.DequeueReusableCell (identifier); 177 | if (cell == null) 178 | { cell = new UITableViewCell (UITableViewCellStyle.Subtitle, identifier); } 179 | 180 | string item = Data[indexPath.Row]; 181 | cell.TextLabel.Text = item; 182 | cell.DetailTextLabel.Text = $"{item.Length} characters"; // C# 6 183 | return cell; 184 | } 185 | } 186 | ``` 187 | 188 | To use this new source with our table, create an instance, assign the data list, and tell the `tableView` to use this as its `Source`: 189 | 190 | ```csharp 191 | source = new DetailSource(); // create the new class 192 | source.Data = data; // assign the list of strings 193 | tableView.Source = source; // give it to the table view (replaces the old one) 194 | tableView.ReloadData(); // reload the data with new cell layout 195 | ``` 196 | 197 | The table rows now contain two pieces of text! 198 | 199 | ![](detailsource.png) 200 | 201 | `UITableViewCellStyle` can also be set to `Value1` and `Value2` which display the text in different ways. 202 | 203 | It is also possible to display an image - the class below sets the `ImageView` property to a local image file (in this case, the same image is used for every row): 204 | 205 | ```csharp 206 | public class ImageSource : MySource 207 | { 208 | string identifier = "imagecell"; 209 | public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath) 210 | { 211 | UITableViewCell cell = tableView.DequeueReusableCell (identifier); 212 | if (cell == null) 213 | { cell = new UITableViewCell (UITableViewCellStyle.Subtitle, identifier); } 214 | 215 | string item = Data[indexPath.Row]; 216 | cell.TextLabel.Text = item; 217 | cell.DetailTextLabel.Text = $"{item.Length} characters"; // C# 6 218 | cell.ImageView.Image = UIImage.FromFile("workicon.png"); // now sets an image 219 | return cell; 220 | } 221 | } 222 | ``` 223 | 224 | Now just configure this new source and assign to the table view: 225 | 226 | ```csharp 227 | source = new ImageSource(); // create the new class 228 | source.Data = data; // assign the list of strings 229 | tableView.Source = source; // give it to the table view (replaces the old one) 230 | tableView.ReloadData(); // reload the data, cells now also display an image 231 | ``` 232 | 233 | The cells now look like this! 234 | 235 | ![](imagesource.png) 236 | 237 | The other aspect of the cell that can be customized is the accessory – special icons that appear on the right side of the cell. Modify the `GetCell` method above with one of these lines of code to add an accessory to each row: 238 | 239 | `cell.Accessory = UITableViewCellAccessory.Checkmark; cell.Accessory = UITableViewCellAccessory.DisclosureIndicator; cell.Accessory = UITableViewCellAccessory.DetailDisclosureButton; cell.Accessory = UITableViewCellAccessory.None; // to clear the accessory` 240 | 241 | ### Custom Cell Layout 242 | 243 | It’s also possible to create totally custom cell layouts, and use them in the `GetCell` method instead of the built-in layouts. Cells are created programmatically by subclassing `UITableViewCell` and overriding these methods: 244 | 245 | * the constructor – create the UI controls in the cell. 246 | 247 | * `LayoutSubviews` – set the location of the UI controls (possibly adjusting them based on the data being displayed). 248 | 249 | It’s helpful to add an additional method to update the data in the cell, which is called UpdateCell in the example. 250 | 251 | ```csharp 252 | public class MyCell : UITableViewCell 253 | { 254 | UILabel headingLabel, subheadingLabel; 255 | UIImageView imageView; 256 | // constructor: create the controls to display 257 | public MyCell (string cellId) : base (UITableViewCellStyle.Default, cellId) 258 | { 259 | ContentView.BackgroundColor = UIColor.FromRGB (218, 255, 127); 260 | imageView = new UIImageView(); 261 | headingLabel = new UILabel () { 262 | Font = UIFont.FromName("Cochin-BoldItalic", 22f), 263 | TextColor = UIColor.FromRGB (127, 51, 0), 264 | BackgroundColor = UIColor.Clear 265 | }; 266 | subheadingLabel = new UILabel () { 267 | Font = UIFont.FromName("AmericanTypewriter", 12f), 268 | TextColor = UIColor.FromRGB (38, 127, 0), 269 | TextAlignment = UITextAlignment.Center, 270 | BackgroundColor = UIColor.Clear 271 | }; 272 | ContentView.AddSubviews(new UIView[] {headingLabel, subheadingLabel, imageView}); 273 | 274 | } 275 | // set the location of each control 276 | public override void LayoutSubviews () 277 | { 278 | base.LayoutSubviews (); 279 | imageView.Frame = new CGRect (ContentView.Bounds.Width - 63, 5, 33, 33); 280 | headingLabel.Frame = new CGRect (5, 4, ContentView.Bounds.Width - 63, 25); 281 | subheadingLabel.Frame = new CGRect (100, 18, 100, 20); 282 | } 283 | // update the cell's data 284 | public void UpdateCell (string caption, string subtitle, UIImage image) 285 | { 286 | imageView.Image = image; 287 | headingLabel.Text = caption; 288 | subheadingLabel.Text = subtitle; 289 | } 290 | } 291 | ``` 292 | 293 | Once again we need to update the source class `GetCell` method to return instances of the custom cell. The `CustomSource` class shown below now creates instances of the custom cell and uses the `UpdateCell` method to pass the data values to display: 294 | 295 | ```csharp 296 | public class CustomSource : MySource 297 | { 298 | string identifier = "imagecell"; 299 | public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath) 300 | { 301 | MyCell cell = tableView.DequeueReusableCell (identifier) as MyCell; 302 | if (cell == null) 303 | { cell = new MyCell (identifier); } 304 | 305 | string item = Data[indexPath.Row]; 306 | cell.UpdateCell(item, 307 | $"{item.Length} characters", 308 | UIImage.FromFile("workicon.png")); 309 | return cell; 310 | } 311 | } 312 | ``` 313 | 314 | Wiring up the new source object works uses the same code as before: 315 | 316 | ```csharp 317 | source = new CustomSource(); // create the new class 318 | source.Data = data; // assign the list of strings 319 | tableView.Source = source; // give it to the table view (replaces the old one) 320 | tableView.ReloadData(); // reload the data, cells now also display an image 321 | ``` 322 | 323 | The custom cells now look like this: 324 | 325 | ![](customsource.png) 326 | 327 | ## Row Selection 328 | 329 | To respond to users touching a row override then `RowSelected` in the source class. The example below shows a source class with the `RowSelected` method added (the rest of the class is unchanged): 330 | 331 | ```csharp 332 | public class SelectionSource : MySource 333 | { 334 | string identifier = "selectioncell"; 335 | public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath) 336 | { 337 | UITableViewCell cell = tableView.DequeueReusableCell (identifier); 338 | if (cell == null) 339 | { cell = new UITableViewCell (UITableViewCellStyle.Subtitle, identifier); } 340 | 341 | string item = Data[indexPath.Row]; 342 | cell.TextLabel.Text = item; 343 | cell.DetailTextLabel.Text = $"{item.Length} characters"; // C# 6 344 | cell.ImageView.Image = UIImage.FromFile("workicon.png"); // now sets an image 345 | return cell; 346 | } 347 | public override void RowSelected (UITableView tableView, NSIndexPath indexPath) 348 | { 349 | var okAlertController = UIAlertController.Create ("Row Selected", 350 | Data[indexPath.Row] + " was touched", 351 | UIAlertControllerStyle.Alert); 352 | okAlertController.AddAction (UIAlertAction.Create("OK", UIAlertActionStyle.Default, null)); 353 | // real apps wouldn't use KeyWindow, this is a "workbook" limitation 354 | KeyWindow.RootViewController.PresentViewController (okAlertController, true, null); 355 | tableView.DeselectRow (indexPath, true); // de-select (otherwise it stays "grey") 356 | } 357 | } 358 | ``` 359 | 360 | The RowSelected method is implemented to show an alert popup when each row is tapped. Each app will implement this differently – perhaps a new view controller is shown instead. Note the last line of the method where `DeselectRow` is called: this is recommended to return the table view to the initial appearance. If the row isn’t deselected, it remains “grey” (or whatever selection-color is defined). 361 | 362 | Wiring it up follows the same pattern as previously. Once this source is used, the rows are “click-able”! 363 | 364 | ```csharp 365 | source = new SelectionSource(); // create the new class 366 | source.Data = data; // assign the list of strings 367 | tableView.Source = source; // give it to the table view (replaces the old one) 368 | tableView.ReloadData(); // reload the data, cells now also display an image 369 | ``` 370 | 371 | The alert will show like this (but you can customize the code to change it 😉): 372 | 373 | ![](selectionsource.png) 374 | 375 | ## Row Actions 376 | 377 | The buttons that are revealed when a row is swiped to the left are called *row actions*. Configuring row actions in a cell (and handling when they are touched) is done by adding another method override to the source class. 378 | 379 | The `ActionSource` class below includes the `EditActionsForRow` method which creates a single action (an array can be supplied to show more than one). 380 | 381 | ```csharp 382 | public class ActionSource : MySource 383 | { 384 | string identifier = "actioncell"; 385 | public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath) 386 | { 387 | UITableViewCell cell = tableView.DequeueReusableCell (identifier); 388 | if (cell == null) 389 | { cell = new UITableViewCell (UITableViewCellStyle.Subtitle, identifier); } 390 | 391 | string item = Data[indexPath.Row]; 392 | cell.TextLabel.Text = item; 393 | cell.DetailTextLabel.Text = $"{item.Length} characters"; // C# 6 394 | cell.ImageView.Image = UIImage.FromFile("workicon.png"); // now sets an image 395 | return cell; 396 | } 397 | public override void RowSelected (UITableView tableView, NSIndexPath indexPath) 398 | { 399 | ShowAlert("Row Selected", Data[indexPath.Row] + " was touched"); 400 | tableView.DeselectRow (indexPath, true); // de-select (otherwise it stays "grey") 401 | } 402 | // refactor out the alert, so it can be used in selection & action 403 | void ShowAlert(string title, string text) 404 | { 405 | var okAlertController = UIAlertController.Create (title, 406 | text, 407 | UIAlertControllerStyle.Alert); 408 | okAlertController.AddAction (UIAlertAction.Create("OK", UIAlertActionStyle.Default, null)); 409 | // real apps wouldn't use KeyWindow, this is a "workbook" limitation 410 | KeyWindow.RootViewController.PresentViewController (okAlertController, true, null); 411 | } 412 | // 413 | public override UITableViewRowAction[] EditActionsForRow (UITableView tableView, NSIndexPath indexPath) 414 | { 415 | var hiAction = UITableViewRowAction.Create ( 416 | UITableViewRowActionStyle.Normal, // try the Destructive style 417 | "Hi", 418 | delegate { 419 | ShowAlert("Action Revealed", Data[indexPath.Row] + " was 'actioned'"); 420 | }); 421 | // multiple actions are allowed 422 | return new UITableViewRowAction[] { hiAction }; 423 | } 424 | } 425 | ``` 426 | 427 | Apply this new source class to the table: 428 | 429 | ```csharp 430 | source = new ActionSource(); // create the new class 431 | source.Data = data; // assign the list of strings 432 | tableView.Source = source; // give it to the table view (replaces the old one) 433 | tableView.ReloadData(); // reload the data, cells now also display an image 434 | ``` 435 | 436 | The action looks like this when revealed by a swipe on the row. This example is grey because `UITableViewRowActionStyle.Normal` was specified in the code. 437 | 438 | ![](actionsource.png) 439 | 440 | `UITableViewRowActionStyle.Destructive` can be specified, which shows the action with a red background. As mentioned earlier, mulitple actions can be shown for a row - but if mulitple actions are shown there should never be more than one *destructive* action. 441 | 442 | ## Edit Mode 443 | 444 | There is another more complex way to interact with tables: edit mode. Edit mode is an optional state for the table which users typically turn on with a button-press. 445 | 446 | Implementing edit mode requires overriding a number of methods in the source class: 447 | 448 | * \*\*CanEditRow \*\*– whether each row can be edited. Return false to prevent both swipe-to-delete and deletion while in edit mode. 449 | 450 | * \*\*CanMoveRow \*\*– return true to enable the move ‘handle’ or false to prevent moving. 451 | 452 | * \*\*EditingStyleForRow \*\*– when the table is in edit mode, the return value from this method determines whether the cell displays the red deletion icon or the green add icon. Return UITableViewCellEditingStyle.None if the row should not be editable. 453 | 454 | * \*\*MoveRow \*\*– called when a row is moved so that the underlying data structure can be modified to match the data as it is displayed in the table. 455 | 456 | * **TitleForDeleteConfirmation** – optional custom text for the delete button. 457 | 458 | * **CommitEditingStyle** – code to apply changes to the underlying data when an edit is complete (ie. if a row is deleted). 459 | 460 | ```csharp 461 | public partial class EditSource : MySource 462 | { 463 | string identifier = "editcell"; 464 | public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath) 465 | { 466 | UITableViewCell cell = tableView.DequeueReusableCell (identifier); 467 | if (cell == null) 468 | { cell = new UITableViewCell (UITableViewCellStyle.Subtitle, identifier); } 469 | 470 | string item = Data[indexPath.Row]; 471 | cell.TextLabel.Text = item; 472 | cell.DetailTextLabel.Text = $"{item.Length} characters"; // C# 6 473 | cell.ImageView.Image = UIImage.FromFile("workicon.png"); // now sets an image 474 | return cell; 475 | } 476 | public override void RowSelected (UITableView tableView, NSIndexPath indexPath) 477 | { 478 | ShowAlert("Row Selected", Data[indexPath.Row] + " was touched"); 479 | tableView.DeselectRow (indexPath, true); // de-select (otherwise it stays "grey") 480 | } 481 | // refactor out the alert, so it can be used in selection & action 482 | void ShowAlert(string title, string text) 483 | { 484 | var okAlertController = UIAlertController.Create (title, 485 | text, 486 | UIAlertControllerStyle.Alert); 487 | okAlertController.AddAction (UIAlertAction.Create("OK", UIAlertActionStyle.Default, null)); 488 | // real apps wouldn't use KeyWindow, this is a "workbook" limitation 489 | KeyWindow.RootViewController.PresentViewController (okAlertController, true, null); 490 | } 491 | public override bool CanEditRow (UITableView tableView, NSIndexPath indexPath) 492 | { 493 | return true; // return false if you wish to disable editing for a specific indexPath or for all rows 494 | } 495 | public override bool CanMoveRow (UITableView tableView, NSIndexPath indexPath) 496 | { 497 | return true; // return false if you don't allow re-ordering 498 | } 499 | public override UITableViewCellEditingStyle EditingStyleForRow (UITableView tableView, NSIndexPath indexPath) 500 | { 501 | return UITableViewCellEditingStyle.Delete; // this example doesn't suppport Insert 502 | } 503 | public override string TitleForDeleteConfirmation (UITableView tableView, NSIndexPath indexPath) 504 | { // Optional - default text is 'Delete' 505 | return "Trash it!"; 506 | } 507 | } 508 | ``` 509 | 510 | ```csharp 511 | public partial class EditSource : MySource 512 | { 513 | public override void CommitEditingStyle (UITableView tableView, UITableViewCellEditingStyle editingStyle, Foundation.NSIndexPath indexPath) 514 | { 515 | switch (editingStyle) // in case others are implemented 516 | { 517 | case UITableViewCellEditingStyle.Delete: 518 | // remove the item from the underlying data source 519 | Data.RemoveAt(indexPath.Row); 520 | // delete the row from the table 521 | tableView.DeleteRows (new NSIndexPath[] { indexPath }, UITableViewRowAnimation.Fade); 522 | break; 523 | } 524 | } 525 | public override void MoveRow (UITableView tableView, NSIndexPath sourceIndexPath, NSIndexPath destinationIndexPath) 526 | { 527 | var item = Data[sourceIndexPath.Row]; 528 | var deleteAt = sourceIndexPath.Row; 529 | var insertAt = destinationIndexPath.Row; 530 | 531 | // are we inserting 532 | if (destinationIndexPath.Row < sourceIndexPath.Row) { 533 | // add one to where we delete, because we're increasing the index by inserting 534 | deleteAt += 1; 535 | } else { 536 | // add one to where we insert, because we haven't deleted the original yet 537 | insertAt += 1; 538 | } 539 | Data.Insert (insertAt, item); 540 | Data.RemoveAt (deleteAt); 541 | } 542 | } 543 | ``` 544 | 545 | Once again, apply the new source to the table: 546 | 547 | ```csharp 548 | source = new EditSource(); // create the new class 549 | source.Data = data; // assign the list of strings 550 | tableView.Source = source; // give it to the table view (replaces the old one) 551 | tableView.ReloadData(); // reload the data, cells now also display an image 552 | ``` 553 | 554 | The table will remain in “normal” mode until editing is enabled. In a “real life” app a button should be provided (typically in the navigation bar), but for this workbook the edit mode can be set dynamically with the following line of code: 555 | 556 | ```csharp 557 | tableView.SetEditing (true, true); // enable edit mode 558 | ``` 559 | 560 | To restore “normal” mode, uncomment and run the following block: 561 | 562 | ```csharp 563 | //tableView.SetEditing (false, true); // disable edit mode (return to normal table behavior) 564 | ``` 565 | 566 | When edit mode is enabled, rows can no longer be “selected” - instead they can be re-ordered with the drag-handle on the right OR the Delete button can be exposed by touching the red-circle on the left. Note that this button is *different* to any row actions that have been defined. 567 | 568 | ![](editsource.png) 569 | 570 | -------------------------------------------------------------------------------- /ScrollingLists/mysource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/ScrollingLists/mysource.png -------------------------------------------------------------------------------- /ScrollingLists/selectionsource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/ScrollingLists/selectionsource.png -------------------------------------------------------------------------------- /ScrollingLists/workicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/ScrollingLists/workicon.png -------------------------------------------------------------------------------- /Strava/ConnectWithStrava.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Strava/ConnectWithStrava.png -------------------------------------------------------------------------------- /Strava/ConnectWithStrava@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Strava/ConnectWithStrava@2x.png -------------------------------------------------------------------------------- /Strava/LogInWithStrava.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Strava/LogInWithStrava.png -------------------------------------------------------------------------------- /Strava/LogInWithStrava@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Strava/LogInWithStrava@2x.png -------------------------------------------------------------------------------- /Strava/README.md: -------------------------------------------------------------------------------- 1 | Sonoma API 2 | ========= 3 | 4 | [Xamarin Workbooks](https://developer.xamarin.com/workbooks/) 5 | demo for the [Strava API](https://www.strava.com/settings/api). 6 | 7 | *Requires a Strava account* 8 | 9 | ![](Screenshots/list-activities-wpf.png) 10 | -------------------------------------------------------------------------------- /Strava/Screenshots/list-activities-wpf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Strava/Screenshots/list-activities-wpf.png -------------------------------------------------------------------------------- /Strava/Screenshots/list-activities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Strava/Screenshots/list-activities.png -------------------------------------------------------------------------------- /Strava/Screenshots/single-activity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Strava/Screenshots/single-activity.png -------------------------------------------------------------------------------- /Strava/StravaApi-mac.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platforms: 4 | - MacMobile 5 | packages: 6 | - id: Newtonsoft.Json 7 | version: 9.0.1 8 | --- 9 | 10 | # Strava API Workbook 11 | 12 | This workbook explains the steps to access the Strava fitness app API. 13 | 14 | 1. **Getting Started** - configure your [Strava API account](https://www.strava.com/settings/api "Strava API settings"), create some helper classes, and test against your own data. 15 | 16 | 2. **OAuth Login** - use a web browser to initiate OAuth login and retrieve an authentication token ![](ConnectWithStrava.png "Connect with Strava") 17 | 18 | 3. **List Activities** - use the OAuth token to access logged-in user’s activities. 19 | 20 | 4. **Get Single Activity** - use the OAuth token to access a single activity. 21 | 22 | Once the OAuth exchange is complete, the resulting token can be used to access all the API endpoints (depending on the scope requested). 23 | 24 | ## 1.Getting Started 25 | 26 | First, register at [strava.com/settings/api](https://www.strava.com/settings/api "Strava.com"). The site will show the settings like this: 27 | 28 | ![Strava API Settings](strava-api-sml.png) 29 | 30 | Copy the details (click *show* to revel the secret and token) into the fields below: 31 | 32 | ```csharp 33 | var clientId = "CLIENT_ID"; 34 | var clientSecret = "SECRET_HERE"; 35 | var token = "TOKEN_HERE"; // allows access to "YOUR public data only" 36 | 37 | // refer to the API documentation before changing these OAuth constants 38 | var redirectUrl = "http://localhost"; 39 | var scope = "view_private"; // default is which means "read only", or "write" which allows updating data 40 | ``` 41 | 42 | ### Web Service Helpers 43 | 44 | Import the Json.Net NuGet package and create some helpers to call the Strava API (including the endpoint URLs): 45 | 46 | ```csharp 47 | #r "Newtonsoft.Json" 48 | using Newtonsoft.Json; 49 | using System.Net; 50 | 51 | WebClient StravaClient (string auth = null) { 52 | WebClient client = new WebClient(); 53 | client.Headers.Add (HttpRequestHeader.Accept, "application/json"); 54 | if (!String.IsNullOrEmpty(auth)) 55 | { 56 | //Console.WriteLine("auth:"+auth); 57 | client.Headers.Add ("Authorization", "Bearer " + auth); 58 | } 59 | return client; 60 | } 61 | 62 | // Strava OAuth config 63 | var redirectUrl = "http://localhost"; 64 | var scope = "view_private"; 65 | 66 | // Strava API endpoint URLs 67 | var exchangeUrl = "https://www.strava.com/oauth/token"; 68 | var athleteUrl = "https://www.strava.com/api/v3/athlete"; 69 | var activitiesUrl = "https://www.strava.com/api/v3/athlete/activities"; 70 | var activityUrl = "https://www.strava.com/api/v3/activities/{0}"; 71 | 72 | // exchangeUrl HTTP-POST parameter list 73 | string postData { get { return $"client_id={clientId}&client_secret={clientSecret}&code={token}";}} 74 | ``` 75 | 76 | ### Model classes 77 | 78 | Strava’s data model is [well-documented](http://strava.github.io/api/ "well-documented"). These C# classes are adorned with correct `JsonProperty` attributes so the Json data returned by the API can be easily deserialized. 79 | 80 | ```csharp 81 | class Gear 82 | { 83 | [JsonProperty("id")] 84 | public string Id {get;set;} 85 | [JsonProperty("primary")] 86 | public bool IsPrimary {get;set;} 87 | [JsonProperty("name")] 88 | public string Name {get;set;} 89 | [JsonProperty("distance")] 90 | public double Distance {get;set;} 91 | [JsonProperty("resource_state")] 92 | public int ResourceState {get;set;} 93 | 94 | public override string ToString() 95 | { 96 | return $"{Name} {Distance/1000}km"; 97 | } 98 | } 99 | class Athlete 100 | { 101 | [JsonProperty("id")] 102 | public int Id {get;set;} 103 | [JsonProperty("username")] 104 | public string Username {get;set;} 105 | [JsonProperty("resource_state")] 106 | public int ResourceState {get;set;} 107 | [JsonProperty("firstname")] 108 | public string FirstName {get;set;} 109 | [JsonProperty("lastname")] 110 | public string LastName {get;set;} 111 | [JsonProperty("profile")] 112 | public string ImageUrl {get;set;} 113 | 114 | [JsonProperty("bikes")] 115 | public Gear[] Bikes {get;set;} 116 | [JsonProperty("shoes")] 117 | public Gear[] Shoes {get;set;} 118 | 119 | public override string ToString() 120 | { 121 | return $"{FirstName} {LastName}"; 122 | } 123 | } 124 | class Activity 125 | { 126 | [JsonProperty("id")] 127 | public int Id {get;set;} 128 | [JsonProperty("name")] 129 | public string Name {get;set;} 130 | [JsonProperty("description")] 131 | public string Description {get;set;} 132 | [JsonProperty("distance")] 133 | public float Distance {get;set;} 134 | [JsonProperty("moving_time")] 135 | public int MovingTimeSeconds {get;set;} 136 | [JsonProperty("kudos_count")] 137 | public int Kudos {get;set;} 138 | 139 | public override string ToString() 140 | { 141 | return $"{Name} {Distance/1000}km ({Kudos})"; 142 | } 143 | } 144 | class TokenResponse 145 | { 146 | [JsonProperty("access_token")] 147 | public string AccessToken {get;set;} 148 | [JsonProperty("token_type")] 149 | public string TokenType {get;set;} 150 | [JsonProperty("athlete")] 151 | public Athlete Athlete {get;set;} 152 | 153 | public override string ToString() 154 | { 155 | return $"Use '{AccessToken}' for all subsequent requests for '{Athlete}'"; 156 | } 157 | } 158 | ``` 159 | 160 | ### Make a Request 161 | 162 | Using the token from the **Settings** page, make a request against the API without going through the login process (which will be required by any apps using the API). 163 | 164 | This will only return data for your own Strava account. 165 | 166 | ```csharp 167 | Athlete me; 168 | try 169 | { 170 | WebClient client = StravaClient(token); // uses the default token 171 | 172 | var response = client.DownloadData (athleteUrl); // no parameters passed 173 | var responseString = System.Text.Encoding.UTF8.GetString(response); 174 | 175 | var js = new JsonSerializer(); 176 | me = JsonConvert.DeserializeObject(responseString); 177 | // Console.WriteLine ("SUCCESS: " + responseString); 178 | } 179 | catch (System.Net.WebException e) 180 | { 181 | Console.WriteLine ("FAILED: " + e.Message); 182 | } 183 | me 184 | ``` 185 | 186 | While it is useful for testing, the default token only lets you view **your** Strava data. To build an app for others, we need to allow them to login and use the resulting token to access their data. 187 | 188 | ## 2.OAuth Login 189 | 190 | Any application requiring the user to login to their Strava account and access their own personal data will have to use OAuth. **To simulate that from this workbook, execute the following block and copy the resulting URL into a web browser (like Chrome or Firefox):** 191 | 192 | ```csharp 193 | var AuthUrl = $"https://www.strava.com/oauth/authorize?client_id={clientId}&response_type=code&redirect_uri={redirectUrl}&scope={scope}"; 194 | ``` 195 | 196 | The web browser will first present the **Strava login page**, and then once you’ve successfully logged-in, redirect to a blank page (“localhost”) with a URL in the format: 197 | 198 | `http://localhost/?state=&code=01234567890abcdef01234567890abcdef` 199 | 200 | **Copy the `code` token from the end of that URL in the web browser, and past it below:** 201 | 202 | ```csharp 203 | // set the token from the browser's URL field here 204 | token = "181d3153f6260f39b3f31286fa6e8db40958618e"; // eg. "01234567890abcdef01234567890abcdef" 205 | ``` 206 | 207 | Complete the OAuth “exchange” by calling the `exchangeUrl` with this token, and retrieving the `AccessToken` from the response to use with all subsequent requests: 208 | 209 | ```csharp 210 | TokenResponse oauthResponse; 211 | try 212 | { 213 | WebClient client = StravaClient(token); // use token from OAuth web browser login 214 | 215 | // send postData parameters that MATCH what was sent in the OAuth web login request! 216 | var response = client.UploadString (exchangeUrl, "POST", postData); 217 | var responseString = response; 218 | 219 | var js = new JsonSerializer(); 220 | oauthResponse = JsonConvert.DeserializeObject(responseString); 221 | // Console.WriteLine ("SUCCESS: " + responseString); 222 | } 223 | catch (System.Net.WebException e) 224 | { 225 | Console.WriteLine ("FAILED: " + e.Message); 226 | } 227 | oauthResponse 228 | ``` 229 | 230 | The response will also include the `Athlete` information - because the token was created by an OAuth web login, the `Athlete` data will reflect the user that logged in. 231 | 232 | ```csharp 233 | oauthResponse.Athlete 234 | ``` 235 | 236 | ## 3.List Activities 237 | 238 | Using the OAuth token, we can now call different API endpoints to retrieve Strava data on behalf of this user. 239 | 240 | ```csharp 241 | Activity[] activityList; 242 | try 243 | { 244 | WebClient client = StravaClient(oauthResponse.AccessToken); // use the token from the OAuth login exchange 245 | 246 | var response = client.DownloadData (activitiesUrl); // GET 247 | var responseString = System.Text.Encoding.UTF8.GetString(response); 248 | 249 | var js = new JsonSerializer(); 250 | activityList = JsonConvert.DeserializeObject(responseString); 251 | //Console.WriteLine ("SUCCESS: " + responseString); 252 | } 253 | catch (System.Net.WebException e) 254 | { 255 | Console.WriteLine ("FAILED: " + e.Message); 256 | } 257 | activityList 258 | ``` 259 | 260 | ## 4.Get Single Activity 261 | 262 | Further requests can be made to drill down into any of the APIs, for example we can choose a specific activity by its identifier: 263 | 264 | ```csharp 265 | activityList[0].Id 266 | ``` 267 | 268 | Making another API call with that `Id` and we can drill down to that `Activity`: 269 | 270 | ```csharp 271 | Activity activity; 272 | var specificActivityUrl = string.Format(activityUrl, activityList[0].Id); 273 | try 274 | { 275 | WebClient client = StravaClient(oauthResponse.AccessToken); // use the token from the OAuth login exchange 276 | 277 | var response = client.DownloadData (specificActivityUrl); // GET 278 | var responseString = System.Text.Encoding.UTF8.GetString(response); 279 | 280 | var js = new JsonSerializer(); 281 | activity = JsonConvert.DeserializeObject(responseString); 282 | //Console.WriteLine ("SUCCESS: " + responseString); 283 | } 284 | catch (System.Net.WebException e) 285 | { 286 | Console.WriteLine ("FAILED: " + e.Message); 287 | } 288 | activity 289 | ``` 290 | 291 | Using this pattern the entire [Strava API](http://strava.github.io/api/ "Strava API") can be explored using C#. -------------------------------------------------------------------------------- /Strava/StravaApi-wpf.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platform: WPF 4 | packages: 5 | - id: Newtonsoft.Json 6 | version: 9.0.1 7 | --- 8 | 9 | # Strava API Workbook 10 | 11 | This workbook explains the steps to access the Strava fitness app API. 12 | 13 | 1. **Getting Started** - configure your [Strava API account](https://www.strava.com/settings/api "Strava API settings"), create some helper classes, and test against your own data. 14 | 15 | 2. **OAuth Login** - use a web browser to initiate OAuth login and retrieve an authentication token ![](ConnectWithStrava.png "Connect with Strava") 16 | 17 | 3. **List Activities** - use the OAuth token to access logged-in user’s activities. 18 | 19 | 4. **Get Single Activity** - use the OAuth token to access a single activity. 20 | 21 | Once the OAuth exchange is complete, the resulting token can be used to access all the API endpoints (depending on the scope requested). 22 | 23 | ## 1.Getting Started 24 | 25 | First, register at [strava.com/settings/api](https://www.strava.com/settings/api "Strava.com"). The site will show the settings like this: 26 | 27 | ![Strava API Settings](strava-api-sml.png) 28 | 29 | Copy the details (click *show* to revel the secret and token) into the fields below: 30 | 31 | ```csharp 32 | var clientId = "CLIENT_ID"; 33 | var clientSecret = "SECRET_HERE"; 34 | var token = "TOKEN_HERE"; // allows access to "YOUR public data only" 35 | 36 | // refer to the API documentation before changing these OAuth constants 37 | var redirectUrl = "http://localhost"; 38 | var scope = "view_private"; // default is which means "read only", or "write" which allows updating data 39 | ``` 40 | 41 | ### Web Service Helpers 42 | 43 | Import the Json.Net NuGet package and create some helpers to call the Strava API (including the endpoint URLs): 44 | 45 | ```csharp 46 | #r "Newtonsoft.Json" 47 | using Newtonsoft.Json; 48 | using System.Net; 49 | 50 | WebClient StravaClient (string auth = null) { 51 | WebClient client = new WebClient(); 52 | client.Headers.Add (HttpRequestHeader.Accept, "application/json"); 53 | if (!String.IsNullOrEmpty(auth)) 54 | { 55 | //Console.WriteLine("auth:"+auth); 56 | client.Headers.Add ("Authorization", "Bearer " + auth); 57 | } 58 | return client; 59 | } 60 | 61 | // Strava OAuth config 62 | var redirectUrl = "http://localhost"; 63 | var scope = "view_private"; 64 | 65 | // Strava API endpoint URLs 66 | var exchangeUrl = "https://www.strava.com/oauth/token"; 67 | var athleteUrl = "https://www.strava.com/api/v3/athlete"; 68 | var activitiesUrl = "https://www.strava.com/api/v3/athlete/activities"; 69 | var activityUrl = "https://www.strava.com/api/v3/activities/{0}"; 70 | 71 | // exchangeUrl HTTP-POST parameter list 72 | string postData { get { return $"client_id={clientId}&client_secret={clientSecret}&code={token}";}} 73 | ``` 74 | 75 | ### Model classes 76 | 77 | Strava’s data model is [well-documented](http://strava.github.io/api/ "well-documented"). These C# classes are adorned with correct `JsonProperty` attributes so the Json data returned by the API can be easily deserialized. 78 | 79 | ```csharp 80 | class Gear 81 | { 82 | [JsonProperty("id")] 83 | public string Id {get;set;} 84 | [JsonProperty("primary")] 85 | public bool IsPrimary {get;set;} 86 | [JsonProperty("name")] 87 | public string Name {get;set;} 88 | [JsonProperty("distance")] 89 | public double Distance {get;set;} 90 | [JsonProperty("resource_state")] 91 | public int ResourceState {get;set;} 92 | 93 | public override string ToString() 94 | { 95 | return $"{Name} {Distance/1000}km"; 96 | } 97 | } 98 | class Athlete 99 | { 100 | [JsonProperty("id")] 101 | public int Id {get;set;} 102 | [JsonProperty("username")] 103 | public string Username {get;set;} 104 | [JsonProperty("resource_state")] 105 | public int ResourceState {get;set;} 106 | [JsonProperty("firstname")] 107 | public string FirstName {get;set;} 108 | [JsonProperty("lastname")] 109 | public string LastName {get;set;} 110 | [JsonProperty("profile")] 111 | public string ImageUrl {get;set;} 112 | 113 | [JsonProperty("bikes")] 114 | public Gear[] Bikes {get;set;} 115 | [JsonProperty("shoes")] 116 | public Gear[] Shoes {get;set;} 117 | 118 | public override string ToString() 119 | { 120 | return $"{FirstName} {LastName}"; 121 | } 122 | } 123 | class Activity 124 | { 125 | [JsonProperty("id")] 126 | public int Id {get;set;} 127 | [JsonProperty("name")] 128 | public string Name {get;set;} 129 | [JsonProperty("description")] 130 | public string Description {get;set;} 131 | [JsonProperty("distance")] 132 | public float Distance {get;set;} 133 | [JsonProperty("moving_time")] 134 | public int MovingTimeSeconds {get;set;} 135 | [JsonProperty("kudos_count")] 136 | public int Kudos {get;set;} 137 | 138 | public override string ToString() 139 | { 140 | return $"{Name} {Distance/1000}km ({Kudos})"; 141 | } 142 | } 143 | class TokenResponse 144 | { 145 | [JsonProperty("access_token")] 146 | public string AccessToken {get;set;} 147 | [JsonProperty("token_type")] 148 | public string TokenType {get;set;} 149 | [JsonProperty("athlete")] 150 | public Athlete Athlete {get;set;} 151 | 152 | public override string ToString() 153 | { 154 | return $"Use '{AccessToken}' for all subsequent requests for '{Athlete}'"; 155 | } 156 | } 157 | ``` 158 | 159 | ### Make a Request 160 | 161 | Using the token from the **Settings** page, make a request against the API without going through the login process (which will be required by any apps using the API). 162 | 163 | This will only return data for your own Strava account. 164 | 165 | ```csharp 166 | Athlete me; 167 | try 168 | { 169 | WebClient client = StravaClient(token); // uses the default token 170 | 171 | var response = client.DownloadData (athleteUrl); // no parameters passed 172 | var responseString = System.Text.Encoding.UTF8.GetString(response); 173 | 174 | var js = new JsonSerializer(); 175 | me = JsonConvert.DeserializeObject(responseString); 176 | // Console.WriteLine ("SUCCESS: " + responseString); 177 | } 178 | catch (System.Net.WebException e) 179 | { 180 | Console.WriteLine ("FAILED: " + e.Message); 181 | } 182 | me 183 | ``` 184 | 185 | While it is useful for testing, the default token only lets you view **your** Strava data. To build an app for others, we need to allow them to login and use the resulting token to access their data. 186 | 187 | ## 2.OAuth Login 188 | 189 | Any application requiring the user to login to their Strava account and access their own personal data will have to use OAuth. **To simulate that from this workbook, execute the following block and copy the resulting URL into a web browser (like Chrome or Firefox):** 190 | 191 | ```csharp 192 | var AuthUrl = $"https://www.strava.com/oauth/authorize?client_id={clientId}&response_type=code&redirect_uri={redirectUrl}&scope={scope}"; 193 | ``` 194 | 195 | The web browser will first present the **Strava login page**, and then once you’ve successfully logged-in, redirect to a blank page (“localhost”) with a URL in the format: 196 | 197 | `http://localhost/?state=&code=01234567890abcdef01234567890abcdef` 198 | 199 | **Copy the `code` token from the end of that URL in the web browser, and past it below:** 200 | 201 | ```csharp 202 | // set the token from the browser's URL field here 203 | token = "-- INSERT &code= parameter HERE --"; // eg. "01234567890abcdef01234567890abcdef" 204 | ``` 205 | 206 | Complete the OAuth “exchange” by calling the `exchangeUrl` with this token, and retrieving the `AccessToken` from the response to use with all subsequent requests: 207 | 208 | ```csharp 209 | TokenResponse oauthResponse; 210 | try 211 | { 212 | WebClient client = StravaClient(token); // use token from OAuth web browser login 213 | 214 | // send postData parameters that MATCH what was sent in the OAuth web login request! 215 | var response = client.UploadString (exchangeUrl, "POST", postData); 216 | var responseString = response; 217 | 218 | var js = new JsonSerializer(); 219 | oauthResponse = JsonConvert.DeserializeObject(responseString); 220 | // Console.WriteLine ("SUCCESS: " + responseString); 221 | } 222 | catch (System.Net.WebException e) 223 | { 224 | Console.WriteLine ("FAILED: " + e.Message); 225 | } 226 | oauthResponse 227 | ``` 228 | 229 | The response will also include the `Athlete` information - because the token was created by an OAuth web login, the `Athlete` data will reflect the user that logged in. 230 | 231 | ```csharp 232 | oauthResponse.Athlete 233 | ``` 234 | 235 | ## 3.List Activities 236 | 237 | Using the OAuth token, we can now call different API endpoints to retrieve Strava data on behalf of this user. 238 | 239 | ```csharp 240 | Activity[] activityList; 241 | try 242 | { 243 | WebClient client = StravaClient(oauthResponse.AccessToken); // use the token from the OAuth login exchange 244 | 245 | var response = client.DownloadData (activitiesUrl); // GET 246 | var responseString = System.Text.Encoding.UTF8.GetString(response); 247 | 248 | var js = new JsonSerializer(); 249 | activityList = JsonConvert.DeserializeObject(responseString); 250 | //Console.WriteLine ("SUCCESS: " + responseString); 251 | } 252 | catch (System.Net.WebException e) 253 | { 254 | Console.WriteLine ("FAILED: " + e.Message); 255 | } 256 | activityList 257 | ``` 258 | 259 | ## 4.Get Single Activity 260 | 261 | Further requests can be made to drill down into any of the APIs, for example we can choose a specific activity by its identifier: 262 | 263 | ```csharp 264 | activityList[0].Id 265 | ``` 266 | 267 | Making another API call with that `Id` and we can drill down to that `Activity`: 268 | 269 | ```csharp 270 | Activity activity; 271 | var specificActivityUrl = string.Format(activityUrl, activityList[0].Id); 272 | try 273 | { 274 | WebClient client = StravaClient(oauthResponse.AccessToken); // use the token from the OAuth login exchange 275 | 276 | var response = client.DownloadData (specificActivityUrl); // GET 277 | var responseString = System.Text.Encoding.UTF8.GetString(response); 278 | 279 | var js = new JsonSerializer(); 280 | activity = JsonConvert.DeserializeObject(responseString); 281 | //Console.WriteLine ("SUCCESS: " + responseString); 282 | } 283 | catch (System.Net.WebException e) 284 | { 285 | Console.WriteLine ("FAILED: " + e.Message); 286 | } 287 | activity 288 | ``` 289 | 290 | Using this pattern the entire [Strava API](http://strava.github.io/api/ "Strava API") can be explored using C#. 291 | -------------------------------------------------------------------------------- /Strava/strava-api-sml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Strava/strava-api-sml.png -------------------------------------------------------------------------------- /Strava/strava-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Strava/strava-api.png -------------------------------------------------------------------------------- /Visualizers/README.md: -------------------------------------------------------------------------------- 1 | Visualizers 2 | ========= 3 | 4 | [Xamarin Workbooks](https://developer.xamarin.com/guides/cross-platform/workbooks/) results visualization examples. 5 | 6 | ![](Screenshots/ios-mac-sml.png) 7 | 8 | These workbooks (one for each platform) demonstrate 9 | 10 | * String 11 | * Object 12 | * Color (UIColor) 13 | * Enumerable 14 | * Map (CLLocation) 15 | * Image (UIImage) 16 | * Exception 17 | * Html 18 | * Help 19 | 20 | visualizations of object results. -------------------------------------------------------------------------------- /Visualizers/Screenshots/ios-mac-sml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Visualizers/Screenshots/ios-mac-sml.png -------------------------------------------------------------------------------- /Visualizers/Screenshots/ios-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Visualizers/Screenshots/ios-mac.png -------------------------------------------------------------------------------- /Visualizers/Visualizers-Console.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platforms: 4 | - Console 5 | --- 6 | 7 | # Visualizers (Console) 8 | 9 | Xamarin Workbooks uses a different visualizers for inline code evaluation results: 10 | 11 | * String 12 | 13 | * Object 14 | 15 | * Enumerable 16 | 17 | * Exception 18 | 19 | * Html 20 | 21 | * Help 22 | 23 | By default, the results of a code-block will shown as a string representation of the *last-referenced object* in the block. The `Monkey` class below demonstrates this: when the `rupert` object is assigned, the **ToString** representation is printed after the code-block. 24 | 25 | ```csharp 26 | class Monkey { 27 | public string Name; 28 | public string Species; 29 | public string Habitat; 30 | public DateTime Birthday = DateTime.MinValue; 31 | public override string ToString(){ 32 | return $"{Name} the {Species} ({Habitat})"; //C# 6 33 | } 34 | } 35 | var rupert = new Monkey {Name="Rupert", Species = "Xamarin", Habitat="San Francisco"}; 36 | ``` 37 | 38 | Use the popup menu to the right of the result to switch to the **Object Members** view. Most code-block results in Workbooks will have both **ToString** and **Object Members** display options. 39 | 40 | DateTime values have a number of display options, including a calendar view: 41 | 42 | ```csharp 43 | rupert.Birthday = new DateTime(2011,05,11); 44 | ``` 45 | 46 | Enumerable collections will be expanded so that you can explore their contents. You can also change the view of each individual object in the collection: 47 | 48 | ```csharp 49 | var enumerable = new List {"alpha", "beta", "gamma", "delta"}; 50 | ``` 51 | 52 | Exceptions have a custom display: 53 | 54 | ```csharp 55 | new ArgumentNullException ("name"); 56 | ``` 57 | 58 | HTML can also be emitted from code-blocks and rendered in the Workbook. This simple example uses string interpolation to customize an HTML string for display using `AsHtml()`: 59 | 60 | ```csharp 61 | var greeting = "Hello, Workbooks"; // C# 6 string interpolation 62 | $"

{greeting}

bold italic underline".AsHtml() 63 | ``` 64 | 65 | There’s also a `help` command, which just lists some handy tips. The help list is slightly different for each platform supported by Workbooks. 66 | 67 | ```csharp 68 | help 69 | ``` 70 | 71 | And finally, not really a visualization, but from the help above you can see that it’s possible to affect the culture of the Workbook. These code-blocks show the date rendered in English and then Spanish (after setting the `CurrentCulture`): 72 | 73 | ```csharp 74 | DateTime.Now.ToLongDateString() 75 | ``` 76 | 77 | ```csharp 78 | CurrentCulture = new System.Globalization.CultureInfo("es"); 79 | DateTime.Now.ToLongDateString() 80 | ``` 81 | 82 | ```csharp 83 | // reset to English 84 | CurrentCulture = new System.Globalization.CultureInfo("en"); 85 | ``` 86 | 87 | ## Custom “Rendering” in the Result Cell 88 | 89 | In addition to the results of a cell being represented as a string, it’s also possible to create a custom representation for a type. The code would included in an external **.csx** file. 90 | 91 | Begin by referencing this namespace: 92 | 93 | ```csharp 94 | using XIR = Xamarin.Interactive.Representations; 95 | ``` 96 | 97 | To add the custom “renderer”, use the `AddProvider` method, specifying 98 | 99 | ```csharp 100 | InteractiveAgent.RepresentationManager.AddProvider (mk => { 101 | // HTML output 102 | return $"{mk.Name} the {mk.Species} monkey lives in {mk.Habitat}".AsHtml(); 103 | }); 104 | InteractiveAgent.RepresentationManager.AddProvider (mk => { 105 | // Plain Text output 106 | return $"{mk.Name} - {mk.Species} monkey ({mk.Habitat})"; 107 | }); 108 | // now any Monkey class is represented by these providers in the workbook. 109 | // multiple providers can be added, and chosen by the reader in the result cell menu 110 | ``` 111 | 112 | Using the `Monkey` object created earlier, choose different result renders from the menu in the result cell: 113 | 114 | ```csharp 115 | rupert 116 | ``` 117 | 118 | > ⚠️ NOTE: because the provider is added each time that code cell is run, duplicate providers will be added if the cell is re-run. Place this code in a **.csx** file for best results. -------------------------------------------------------------------------------- /Visualizers/Visualizers-Mac.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platform: MacNet45 4 | packages: [] 5 | --- 6 | 7 | # Visualizers for Mac 8 | 9 | Xamarin Workbooks uses a different visualizers for inline code evaluation results: 10 | 11 | * String 12 | 13 | * Object 14 | 15 | * Color (`NSColor`) 16 | 17 | * Enumerable 18 | 19 | * Map (`CLLocation`) 20 | 21 | * Image (`NSImage`) 22 | 23 | * Exception 24 | 25 | * Html 26 | 27 | * Help 28 | 29 | By default, the results of a code-block will shown as a string representation of the *last-referenced object* in the block. The `Monkey` class below demonstrates this: when the `rupert` object is assigned, the **ToString** representation is printed after the code-block. 30 | 31 | ```csharp 32 | class Monkey { 33 | public string Name; 34 | public string Species; 35 | public string Habitat; 36 | public CoreLocation.CLLocation Location = null; 37 | public NSColor Color = null; 38 | public DateTime Birthday = DateTime.MinValue; 39 | public override string ToString(){ 40 | return $"{Name} the {Species} ({Habitat})"; //C# 6 41 | } 42 | } 43 | var rupert = new Monkey {Name="Rupert", Species = "Xamarin", Habitat="San Francisco"}; 44 | ``` 45 | 46 | Use the popup menu to the right of the result to switch to the **Object Members** view. Most code-block results in Workbooks will have both **ToString** and **Object Members** display options. 47 | 48 | Color types are rendered with an example of the color: 49 | 50 | ```csharp 51 | rupert.Color = NSColor.Brown; 52 | ``` 53 | 54 | DateTime values have a number of display options, including a calendar view: 55 | 56 | ```csharp 57 | rupert.Birthday = new DateTime(2011,05,11); 58 | ``` 59 | 60 | Enumerable collections will be expanded so that you can explore their contents. You can also change the view of each individual object in the collection: 61 | 62 | ```csharp 63 | var enumerable = new List {"alpha", "beta", "gamma", "delta"}; 64 | ``` 65 | 66 | Location objects can be viewed on a map: 67 | 68 | ```csharp 69 | rupert.Location = new CoreLocation.CLLocation(37.7749,-122.4194); 70 | ``` 71 | 72 | Image data can be downloaded or opened from the local filesystem, then previewed inline with the **Image** view: 73 | 74 | ```csharp 75 | var localPath = "/Users/craigdunn/ProjectsConceptdev/xamarin-workbook-samples/Visualizers/"; 76 | new NSImage(localPath+"bridge.jpg"); 77 | ``` 78 | 79 | Exceptions have a custom display: 80 | 81 | ```csharp 82 | new ArgumentNullException ("name"); 83 | ``` 84 | 85 | HTML can also be emitted from code-blocks and rendered in the Workbook. This simple example uses string interpolation to customize an HTML string for display using `AsHtml()`: 86 | 87 | ```csharp 88 | var greeting = "Hello, Workbooks"; // C# 6 string interpolation 89 | $"

{greeting}

bold italic underline".AsHtml() 90 | ``` 91 | 92 | There’s also a `help` command, which just lists some handy tips. The help list is slightly different for each platform supported by Workbooks. 93 | 94 | ```csharp 95 | help 96 | ``` 97 | 98 | And finally, not really a visualization, but from the help above you can see that it’s possible to affect the culture of the Workbook. These code-blocks show the date rendered in English and then Spanish (after setting the `CurrentCulture`): 99 | 100 | ```csharp 101 | DateTime.Now.ToLongDateString() 102 | ``` 103 | 104 | ```csharp 105 | CurrentCulture = new System.Globalization.CultureInfo("es"); 106 | DateTime.Now.ToLongDateString() 107 | ``` 108 | 109 | ```csharp 110 | // reset to English 111 | CurrentCulture = new System.Globalization.CultureInfo("en"); 112 | ``` -------------------------------------------------------------------------------- /Visualizers/Visualizers-WPF.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platform: WPF 4 | packages: [] 5 | --- 6 | 7 | # Visualizers for WPF 8 | 9 | Xamarin Workbooks uses a different visualizers for inline code evaluation results: 10 | 11 | * String 12 | 13 | * Object 14 | 15 | * Color (`Color`) 16 | 17 | * Enumerable 18 | 19 | * Image (`Bitmap`) 20 | 21 | * Exception 22 | 23 | * Html 24 | 25 | * Help 26 | 27 | By default, the results of a code-block will shown as a string representation of the *last-referenced object* in the block. The `Monkey` class below demonstrates this: when the `rupert` object is assigned, the **ToString** representation is printed after the code-block. 28 | 29 | ```csharp 30 | class Monkey { 31 | public string Name; 32 | public string Species; 33 | public string Habitat; 34 | public System.Drawing.Color Color = System.Drawing.Color.White; 35 | public DateTime Birthday = DateTime.MinValue; 36 | public override string ToString(){ 37 | return $"{Name} the {Species} ({Habitat})"; //C# 6 38 | } 39 | } 40 | var rupert = new Monkey {Name="Rupert", Species = "Xamarin", Habitat="San Francisco"}; 41 | ``` 42 | 43 | Use the popup menu to the right of the result to switch to the **Object Members** view. Most code-block results in Workbooks will have both \*\*ToString\*\* and **Object Members** display options. 44 | 45 | Color types are rendered with an example of the color: 46 | 47 | ```csharp 48 | rupert.Color = System.Drawing.Color.Brown; 49 | ``` 50 | 51 | DateTime values have a number of display options, including a calendar view: 52 | 53 | ```csharp 54 | rupert.Birthday = new DateTime(2011,05,11); 55 | ``` 56 | 57 | Enumerable collections will be expanded so that you can explore their contents. You can also change the view of each individual object in the collection: 58 | 59 | ```csharp 60 | var enumerable = new List {"alpha", "beta", "gamma", "delta"}; 61 | ``` 62 | 63 | Image data can be downloaded or opened from the local filesystem, then previewed inline with the **Image** view: 64 | 65 | ```csharp 66 | var localPath = @"C:\ProjectsConceptdev\xamarin-workbook-samples\Visualizers\"; 67 | new System.Drawing.Bitmap(localPath+"bridge.jpg"); 68 | ``` 69 | 70 | Exceptions have a custom display: 71 | 72 | ```csharp 73 | new ArgumentNullException ("name"); 74 | ``` 75 | 76 | HTML can also be emitted from code-blocks and rendered in the Workbook. This simple example uses string interpolation to customize an HTML string for display using `AsHtml()`: 77 | 78 | ```csharp 79 | var greeting = "Hello, Workbooks"; // C# 6 string interpolation 80 | $"

{greeting}

bold italic underline".AsHtml() 81 | ``` 82 | 83 | There’s also a `help` command, which just lists some handy tips. The help list is slightly different for each platform supported by Workbooks. 84 | 85 | ```csharp 86 | help 87 | ``` 88 | 89 | And finally, not really a visualization, but from the help above you can see that it’s possible to affect the culture of the Workbook. These code-blocks show the date rendered in English and then Spanish (after setting the `CurrentCulture`): 90 | 91 | ```csharp 92 | DateTime.Now.ToLongDateString() 93 | ``` 94 | 95 | ```csharp 96 | CurrentCulture = new System.Globalization.CultureInfo("es"); 97 | DateTime.Now.ToLongDateString() 98 | ``` 99 | 100 | ```csharp 101 | // reset to English 102 | CurrentCulture = new System.Globalization.CultureInfo("en"); 103 | ``` -------------------------------------------------------------------------------- /Visualizers/Visualizers-iOS.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platforms: 4 | - iOS 5 | --- 6 | 7 | # Visualizers for iOS 8 | 9 | Xamarin Workbooks uses a different visualizers for inline code evaluation results: 10 | 11 | * String 12 | 13 | * Object 14 | 15 | * Color (`UIColor`) 16 | 17 | * Enumerable 18 | 19 | * Map (`CLLocation`) 20 | 21 | * Image (`UIImage`) 22 | 23 | * Argument 24 | 25 | * Html 26 | 27 | * Help 28 | 29 | By default, the results of a code-block will shown as a string representation of the *last-referenced object* in the block. The `Monkey` class below demonstrates this: when the `rupert` object is assigned, the **ToString** representation is printed after the code-block. 30 | 31 | ```csharp 32 | class Monkey { 33 | public string Name; 34 | public string Species; 35 | public string Habitat; 36 | public CoreLocation.CLLocation Location = null; 37 | public UIColor Color = null; 38 | public DateTime Birthday = DateTime.MinValue; 39 | public override string ToString(){ 40 | return $"{Name} the {Species} ({Habitat})"; //C# 6 41 | } 42 | } 43 | var rupert = new Monkey {Name="Rupert", Species = "Xamarin", Habitat="San Francisco"}; 44 | ``` 45 | 46 | Use the popup menu to the right of the result to switch to the **Object Members** view. Most code-block results in Workbooks will have both \*\*ToString \*\*and **Object Members** display options. 47 | 48 | Color types are rendered with an example of the color: 49 | 50 | ```csharp 51 | rupert.Color = UIColor.Brown; 52 | ``` 53 | 54 | DateTime values have a number of display options, including a calendar view: 55 | 56 | ```csharp 57 | rupert.Birthday = new DateTime(2011,05,11); 58 | ``` 59 | 60 | Enumerable collections will be expanded so that you can explore their contents. You can also change the view of each individual object in the collection: 61 | 62 | ```csharp 63 | var enumerable = new List {"alpha", "beta", "gamma", "delta"}; 64 | ``` 65 | 66 | Location objects can be viewed on a map: 67 | 68 | ```csharp 69 | rupert.Location = new CoreLocation.CLLocation(37.7749,-122.4194); 70 | ``` 71 | 72 | Image data can be downloaded or opened from the local filesystem, then previewed inline with the **Image** view: 73 | 74 | ```csharp 75 | var localPath = "/Users/craigdunn/ProjectsConceptdev/xamarin-workbook-samples/Visualizers/"; 76 | UIImage.FromFile(localPath+"bridge.jpg"); 77 | ``` 78 | 79 | Exceptions have a custom display: 80 | 81 | ```csharp 82 | new ArgumentNullException ("name"); 83 | ``` 84 | 85 | HTML can also be emitted from code-blocks and rendered in the Workbook. This simple example uses string interpolation to customize an HTML string for display using `AsHtml()`: 86 | 87 | ```csharp 88 | var greeting = "Hello, Workbooks"; // C# 6 string interpolation 89 | $"

{greeting}

bold italic underline".AsHtml() 90 | ``` 91 | 92 | There’s also a `help` command, which just lists some handy tips. The help list is slightly different for each platform supported by Workbooks. 93 | 94 | ```csharp 95 | help 96 | ``` 97 | 98 | And finally, not really a visualization, but from the help above you can see that it’s possible to affect the culture of the Workbook. These code-blocks show the date rendered in English and then Spanish (after setting the `CurrentCulture`): 99 | 100 | ```csharp 101 | DateTime.Now.ToLongDateString() 102 | ``` 103 | 104 | ```csharp 105 | CurrentCulture = new System.Globalization.CultureInfo("es"); 106 | DateTime.Now.ToLongDateString() 107 | ``` 108 | 109 | ```csharp 110 | // reset to English 111 | CurrentCulture = new System.Globalization.CultureInfo("en"); 112 | ``` -------------------------------------------------------------------------------- /Visualizers/bridge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Visualizers/bridge.jpg -------------------------------------------------------------------------------- /Wpf/WpfPermissionTest.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platforms: 4 | - WPF 5 | --- 6 | 7 | # WPF Permission Test 8 | 9 | A customer at MVP Summit was getting a **Permission Denied** error in a workbook. Trying to reproduce the problem here (although it seems to work fine for me - the customer may have been using the path incorrectly?) 10 | 11 | ```csharp 12 | var path1 = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); 13 | path1 14 | ``` 15 | 16 | ```csharp 17 | var path2 = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); 18 | path2 19 | ``` 20 | 21 | ```csharp 22 | System.IO.File.WriteAllText (path1 + @"\test1.txt", "This is a test 1"); 23 | ``` 24 | 25 | ```csharp 26 | System.IO.File.WriteAllText (path2 + @"\test2.txt", "This is a test 2"); 27 | ``` 28 | 29 | ```csharp 30 | var contents1 = System.IO.File.ReadAllText (path1 + @"\test1.txt"); 31 | contents1 32 | ``` 33 | 34 | ```csharp 35 | var contents2 = System.IO.File.ReadAllText (path2 + @"\test2.txt"); 36 | contents2 37 | ``` 38 | 39 | Hmm that’s a bit repetitive, so make a helper method: 40 | 41 | ```csharp 42 | void Test (System.Environment.SpecialFolder envPath, string text) 43 | { 44 | var testPath = Environment.GetFolderPath(envPath); 45 | try 46 | { 47 | Console.Write ("Testing " + testPath); 48 | System.IO.File.WriteAllText (testPath + @"\test-wpf-permissions.txt", text); 49 | Console.WriteLine (" ... saved"); 50 | //var contents = System.IO.File.ReadAllText (envPath + @"\testest-wpf-permissionst2.txt"); 51 | } catch (Exception e) { 52 | Console.WriteLine ("... errored " + e.Message); 53 | } 54 | } 55 | ``` 56 | 57 | Test some others from [MSDN](https://msdn.microsoft.com/en-us/library/system.environment.specialfolder(v=vs.110).aspx) … `C:\WINDOWS` and `C:\WINDOWS\System32` fail (as you’d expect) 58 | 59 | ```csharp 60 | Test (Environment.SpecialFolder.Personal,"Personal"); 61 | Test (Environment.SpecialFolder.MyDocuments,"MyDocuments"); 62 | Test (Environment.SpecialFolder.Desktop,"Desktop"); 63 | Test (Environment.SpecialFolder.CommonDocuments,"CommonDocuments"); 64 | Test (Environment.SpecialFolder.CommonPictures,"CommonPictures"); 65 | Test (Environment.SpecialFolder.UserProfile,"UserProfile"); 66 | Test (Environment.SpecialFolder.System,"System"); // fails as expected 67 | Test (Environment.SpecialFolder.Windows,"Windows"); // fails as expected 68 | ``` 69 | 70 | Search for **test-wpf-permissions.txt** to delete the temp files. -------------------------------------------------------------------------------- /Xamarin.Forms/ListView1-android.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platform: Android 4 | packages: 5 | - id: Newtonsoft.Json 6 | version: 8.0.3 7 | - id: Xamarin.Forms 8 | version: 2.2.0.31 9 | --- 10 | 11 | # Xamarin.Forms REST —> ListView 12 | 13 | ## A little hack to demo Xamarin.Forms on iOS with Workbooks... 14 | 15 | ...borrowing some ideas from James Montemagno’s [sample](https://github.com/jamesmontemagno/MonkeysApp-AppIndexing "Monkey Sample App") 16 | 17 | ### Wire up a Xamarin.Forms app 18 | 19 | 1) Start by importing the Nugets for Xamarin.Forms and the iOS Platform Renderers (and Json.Net for some parsing later on): 20 | 21 | ```csharp 22 | #r "Xamarin.Forms.Platform.iOS" 23 | #r "Xamarin.Forms.Core" 24 | #r "Xamarin.Forms.Xaml" 25 | #r "Xamarin.Forms.Platform" 26 | #r "Newtonsoft.Json" 27 | ``` 28 | 29 | 2) We need `using` statements too: 30 | 31 | ```csharp 32 | using Xamarin.Forms; 33 | using Xamarin.Forms.Platform.iOS; 34 | ``` 35 | 36 | 3) Set up a simple page with an exposed property to edit (in this case, a `ListView` which will be populated later): 37 | 38 | ```csharp 39 | public class MyPage : ContentPage 40 | { 41 | public ListView MyList {get;set;} 42 | public MyPage () 43 | { 44 | Title = "A test"; 45 | 46 | MyList = new ListView (); // no data yet, though 47 | 48 | Content = MyList; 49 | } 50 | } 51 | ``` 52 | 53 | 4) Ok great - but we can’t see anything! Oh, we need to bootstrap the Xamarin.Forms app object too: 54 | 55 | ```csharp 56 | public class App : Application 57 | { 58 | public MyPage Page {get;set;} 59 | public App () 60 | { 61 | Page = new MyPage(); 62 | MainPage = new NavigationPage (Page); 63 | } 64 | } 65 | ``` 66 | 67 | 5) Now - here comes the “hack” - sneakily bypass requiring the `FormsApplicationDelegate` subclass (thank goodness it’s [open source](https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.iOS/FormsApplicationDelegate.cs "open source")) and just set the iOS root view controller directly: 68 | 69 | ```csharp 70 | Xamarin.Forms.Forms.Init(); 71 | var a = new App(); 72 | KeyWindow.RootViewController = a.MainPage.CreateViewController(); 73 | ``` 74 | 75 | ⚠️ *YMMV with some Xamarin.Forms features when hacking forms to start-up like this*\ 76 | *BE WARNED!* 77 | 78 | ### Getting the Monkey Data 79 | 80 | 6) Now we have a Xamarin.Forms app, page, and listview - but no data! Because the monkey data lives in a Json file on the internet, we’re going to need some additional namespaces (and a Monkey model class): 81 | 82 | ```csharp 83 | using Newtonsoft.Json; 84 | using System.Net; 85 | 86 | public class Monkey 87 | { 88 | public string Name { get; set; } 89 | public string Location { get; set; } 90 | public string Details { get; set; } 91 | public string Image { get; set; } 92 | public int Population { get; set; } 93 | } 94 | ``` 95 | 96 | 7) Now let’s grab that monkey data from github - the Json file is downloaded and deserialized in just a few lines of code: 97 | 98 | ```csharp 99 | WebClient client = new WebClient(); 100 | var response = client.DownloadData ("https://raw.githubusercontent.com/jamesmontemagno/MonkeysApp-AppIndexing/master/MonkeysApp/monkeydata.json"); // GET 101 | var json = System.Text.Encoding.UTF8.GetString(response); 102 | var monkeys = JsonConvert.DeserializeObject>(json); 103 | ``` 104 | 105 | 8) Now bind that data to the listview - using the built-in `ImageCell` data template - and assigning properties from the monkey model to the Text, Detail, and ImageSource properties of the cell: 106 | 107 | ```csharp 108 | a.Page.MyList.ItemTemplate = new DataTemplate(typeof(ImageCell)); 109 | a.Page.MyList.ItemTemplate.SetBinding(TextCell.TextProperty, "Name"); 110 | a.Page.MyList.ItemTemplate.SetBinding(ImageCell.DetailProperty, "Location"); 111 | a.Page.MyList.ItemTemplate.SetBinding(ImageCell.ImageSourceProperty, "Image"); 112 | a.Page.MyList.ItemsSource = monkeys; 113 | ``` 114 | 115 | 9) And now the iOS app is populated with a list of monkeys! One last thing, update the app title 116 | 117 | ```csharp 118 | a.Page.Title = "Monkeys!"; 119 | ``` 120 | 121 | ### The End Result 122 | 123 | (for static-content view) 124 | 125 | ![finished app](Screenshots/ListView1.png) 126 | -------------------------------------------------------------------------------- /Xamarin.Forms/ListView1-ios.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platform: iOS 4 | packages: 5 | - id: Newtonsoft.Json 6 | version: 8.0.3 7 | - id: Xamarin.Forms 8 | version: 2.2.0.31 9 | --- 10 | 11 | # Xamarin.Forms REST —> ListView 12 | 13 | ## A little hack to demo Xamarin.Forms on iOS with Workbooks... 14 | 15 | ...borrowing some ideas from James Montemagno’s [sample](https://github.com/jamesmontemagno/MonkeysApp-AppIndexing "Monkey Sample App") 16 | 17 | ### Wire up a Xamarin.Forms app 18 | 19 | 1. Start by importing the Nugets for Xamarin.Forms and the iOS Platform Renderers (and Json.Net for some parsing later on): 20 | 21 | ```csharp 22 | #r "Xamarin.Forms.Platform.iOS" 23 | #r "Xamarin.Forms.Core" 24 | #r "Xamarin.Forms.Xaml" 25 | #r "Xamarin.Forms.Platform" 26 | #r "Newtonsoft.Json" 27 | ``` 28 | 29 | 1. We need `using` statements too: 30 | 31 | ```csharp 32 | using Xamarin.Forms; 33 | using Xamarin.Forms.Platform.iOS; 34 | ``` 35 | 36 | 1. Set up a simple page with an exposed property to edit (in this case, a `ListView` which will be populated later): 37 | 38 | ```csharp 39 | public class MyPage : ContentPage 40 | { 41 | public ListView MyList {get;set;} 42 | public MyPage () 43 | { 44 | Title = "A test"; 45 | 46 | MyList = new ListView (); // no data yet, though 47 | 48 | Content = MyList; 49 | } 50 | } 51 | ``` 52 | 53 | 1. Ok great - but we can’t see anything! Oh, we need to bootstrap the Xamarin.Forms app object too: 54 | 55 | ```csharp 56 | public class App : Application 57 | { 58 | public MyPage Page {get;set;} 59 | public App () 60 | { 61 | Page = new MyPage(); 62 | MainPage = new NavigationPage (Page); 63 | } 64 | } 65 | ``` 66 | 67 | 1. Now - here comes the “hack” - sneakily bypass requiring the `FormsApplicationDelegate` subclass (thank goodness it’s [open source](https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.iOS/FormsApplicationDelegate.cs "open source")) and just set the iOS root view controller directly: 68 | 69 | ```csharp 70 | Xamarin.Forms.Forms.Init(); 71 | var a = new App(); 72 | KeyWindow.RootViewController = a.MainPage.CreateViewController(); 73 | ``` 74 | 75 | ⚠️ *YMMV with some Xamarin.Forms features when hacking forms to start-up like this*\ 76 | *BE WARNED!* 77 | 78 | ### Getting the Monkey Data 79 | 80 | 1. Now we have a Xamarin.Forms app, page, and listview - but no data! Because the monkey data lives in a Json file on the internet, we’re going to need some additional namespaces (and a Monkey model class): 81 | 82 | ```csharp 83 | using Newtonsoft.Json; 84 | using System.Net; 85 | 86 | public class Monkey 87 | { 88 | public string Name { get; set; } 89 | public string Location { get; set; } 90 | public string Details { get; set; } 91 | public string Image { get; set; } 92 | public int Population { get; set; } 93 | } 94 | ``` 95 | 96 | 1. Now let’s grab that monkey data from github - the Json file is downloaded and deserialized in just a few lines of code: 97 | 98 | ```csharp 99 | WebClient client = new WebClient(); 100 | var response = client.DownloadData ("https://raw.githubusercontent.com/jamesmontemagno/MonkeysApp-AppIndexing/master/MonkeysApp/monkeydata.json"); // GET 101 | var json = System.Text.Encoding.UTF8.GetString(response); 102 | var monkeys = JsonConvert.DeserializeObject>(json); 103 | ``` 104 | 105 | 1. Now bind that data to the listview - using the built-in `ImageCell` data template - and assigning properties from the monkey model to the Text, Detail, and ImageSource properties of the cell: 106 | 107 | ```csharp 108 | a.Page.MyList.ItemTemplate = new DataTemplate(typeof(ImageCell)); 109 | a.Page.MyList.ItemTemplate.SetBinding(TextCell.TextProperty, "Name"); 110 | a.Page.MyList.ItemTemplate.SetBinding(ImageCell.DetailProperty, "Location"); 111 | a.Page.MyList.ItemTemplate.SetBinding(ImageCell.ImageSourceProperty, "Image"); 112 | a.Page.MyList.ItemsSource = monkeys; 113 | ``` 114 | 115 | 1. And now the iOS app is populated with a list of monkeys! One last thing, update the app title 116 | 117 | ```csharp 118 | a.Page.Title = "Monkeys!"; 119 | ``` 120 | 121 | ### The End Result 122 | 123 | \(for static-content view) 124 | 125 | ![finished app](Screenshots/ListView1.png) 126 | 127 | -------------------------------------------------------------------------------- /Xamarin.Forms/LoadXaml.workbook: -------------------------------------------------------------------------------- 1 | --- 2 | uti: com.xamarin.workbook 3 | platform: iOS 4 | packages: 5 | - id: Newtonsoft.Json 6 | version: 8.0.3 7 | - id: Xamarin.Forms.Dynamic 8 | version: 0.1.18-pre 9 | - id: Xamarin.Forms 10 | version: 2.2.0.31 11 | --- 12 | 13 | # Xamarin.Forms XAML Workbook test 14 | 15 | ## Another little hack to demo Xamarin.Forms XAML on iOS with Workbooks... 16 | 17 | There’s currently no way to import or reference XAML files from a Workbook, so it might seem impossible to use XAML in a demo... however with a little hacking it’s amazing what you can accomplish. 18 | 19 | 🚫 DO NOT TRY THIS AT HOME 😉 20 | 21 | 1. Start by importing the Nugets for Xamarin.Forms and the iOS Platform Renderers 22 | 23 | ```csharp 24 | #r "Xamarin.Forms.Platform.iOS" 25 | #r "Xamarin.Forms.Core" 26 | #r "Xamarin.Forms.Xaml" 27 | #r "Xamarin.Forms.Platform" 28 | ``` 29 | 30 | And for this hack to work, add kzu’s [Dynamic Xamarin Forms](http://www.cazzulino.com/dynamic-forms.html) Nuget (which contains the magic to load XAML from a string): 31 | 32 | ```csharp 33 | #r "Xamarin.Forms.Dynamic" 34 | ``` 35 | 36 | 1. Add the `using` statements next: 37 | 38 | ```csharp 39 | using Xamarin.Forms; 40 | using Xamarin.Forms.Platform.iOS; 41 | ``` 42 | 43 | 1. Now write up a simple XAML `ContentPage`to render on iOS: 44 | 45 | ```csharp 46 | static string xaml = @" 47 | 51 | 52 | 55 | "; 56 | ``` 57 | 58 | 1. Now for the hack: bootstrap the Xamarin.Forms app object and and for the main page class, use kzu’s magic to create it from the `xaml` string: 59 | 60 | ```csharp 61 | public class App : Application 62 | { 63 | public ContentPage XamlPage {get;set;} 64 | public App () 65 | { 66 | XamlPage = new ContentPage(); 67 | XamlPage.LoadFromXaml (xaml); // hack :) 68 | 69 | MainPage = XamlPage; 70 | } 71 | } 72 | ``` 73 | 74 | 1. Finally, use the other “hack” - sneakily bypass requiring the `FormsApplicationDelegate` subclass - and set the iOS root view controller directly: 75 | 76 | ```csharp 77 | Xamarin.Forms.Forms.Init(); 78 | var a = new App(); 79 | KeyWindow.RootViewController = a.MainPage.CreateViewController(); 80 | ``` 81 | 82 | ⚠️ *YMMV with some Xamarin.Forms features when hacking forms to start-up like this* 83 | 84 | ## One More Thing... 85 | 86 | Loading XAML in this way does not allow strongly-typed access to the elements by their `x:Name`, instead they can only be referenced using `FindByName`as shown here to update the label: 87 | 88 | ```csharp 89 | var l = a.XamlPage.FindByName("helloLabel"); 90 | l.Text = "Updated by the Workbook!"; 91 | a.XamlPage.Content 92 | ``` 93 | 94 | -------------------------------------------------------------------------------- /Xamarin.Forms/Screenshots/ListView1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Xamarin.Forms/Screenshots/ListView1.png -------------------------------------------------------------------------------- /Xamarin.Forms/Screenshots/WorkbooksFormsTest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conceptdev/xamarin-workbook-samples/751f8b81e9bf47d551146401d174b7e50d3a685c/Xamarin.Forms/Screenshots/WorkbooksFormsTest.png -------------------------------------------------------------------------------- /Xamarin.Forms/WorkbookFormsTest.workbook: -------------------------------------------------------------------------------- 1 | ```json 2 | {"exec-mode":"default","platform":"iOS","uti":"com.xamarin.workbook","packages":[{"id":"Xamarin.Forms","version":"2.2.0.31"}]} 3 | ``` 4 | 5 | # Xamarin.Forms Workbook test 6 | 7 | ## A little hack to demo Xamarin.Forms on iOS with Workbooks... 8 | 9 | 1. Start by importing the Nugets for Xamarin.Forms and the iOS Platform Renderers 10 | 11 | ```csharp 12 | #r "Xamarin.Forms.Platform.iOS" 13 | #r "Xamarin.Forms.Core" 14 | #r "Xamarin.Forms.Xaml" 15 | #r "Xamarin.Forms.Platform" 16 | ``` 17 | 18 | 2. We need `using` statements too: 19 | 20 | ```csharp 21 | using Xamarin.Forms; 22 | using Xamarin.Forms.Platform.iOS; 23 | ``` 24 | 25 | 3. Set up a simple page with an exposed property for us to edit: 26 | 27 | ```csharp 28 | public class MyPage : ContentPage 29 | { 30 | public Label Hi {get;set;} 31 | public MyPage () 32 | { 33 | Title = "A test"; 34 | 35 | Hi = new Label {Text = "Hello, Workbooks"}; 36 | 37 | var s = new StackLayout{Children = {Hi}}; 38 | 39 | Content = s; 40 | } 41 | } 42 | ``` 43 | 44 | 4. Ok great - but we can’t see anything! Oh, we need to bootstrap the Xamarin.Forms app object too: 45 | 46 | ```csharp 47 | public class App : Application 48 | { 49 | public MyPage MP {get;set;} 50 | public App () 51 | { 52 | MP = new MyPage(); 53 | MainPage = new NavigationPage (MP); 54 | } 55 | } 56 | ``` 57 | 58 | 5. Now - here comes the “hack” - sneakily bypass requiring the `FormsApplicationDelegate` subclass (thank goodness it’s [open source](https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.iOS/FormsApplicationDelegate.cs "open source")) and just set the iOS root view controller directly: 59 | 60 | ```csharp 61 | Xamarin.Forms.Forms.Init(); 62 | var a = new App(); 63 | KeyWindow.RootViewController = a.MainPage.CreateViewController(); 64 | ``` 65 | 66 | ⚠️ *YMMV with some Xamarin.Forms features when hacking forms to start-up like this - *\ 67 | *BE WARNED!* 68 | 69 | 6. To prove it works, modify properties and hit ``: 70 | 71 | ```csharp 72 | a.MP.Title = "Another test"; 73 | a.MP.Hi.Text = "Goodbye"; 74 | ``` 75 | 76 | --------------------------------------------------------------------------------