Developing with Salesforce
Access Salesforce Data using the REST APIs with .NET Code
Force.com-Toolkit-for-NET
The Salesforce Connected Service configures your project to work with the Force.com-Toolkit-for-NET
Within the Force.com-Toolkit-for-NET you'll find additional samples for how to use the SDK and program against the Salesforce REST APIs
Code Samples
Using the following walkthroughs and code snippets, you should be able to quickly get started working with the Salesforce RESTful services. For more information see Salesforce REST Documentation.
Creating an MVC app to view, edit and delete Salesforce Contacts
ASP.net MVC apps follow the Model-View-Controller pattern.
In the following steps, you'll create:
- An MVC Controller to retrieve the Contact object and provide Query, Create, Update and Delete functionality. Within the Controller, we'll validate the connection is authenticated. If not, the user will be redirected to login.
- A Menu in the common Layouts folder for listing your Salesforce Contacts.
- 4 Views using razor syntax for Index, Create, Update and Delete. The Delete View will include a confirmation view as best practices include confirming the user really wanted to perform a destructive action.
- This sample assumes you selected the Contact Salesforce Object in the scaffolding step of the wizard.
Add MVC Controller
- Right click on the Controllers folder and select Add-->Controller...
- Select MVC 5 Controller - Empty
- Name it: ContactsController
- Replace the contents of the Controller with the following code:
- Replace the namespace WebApplication1 with the namespace of your project
using Salesforce.Common.Models; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; using System.Web; using System.Web.Mvc; using WebApplication1.Models.Salesforce; using WebApplication1.Salesforce; namespace WebApplication1.Controllers { public class ContactsController : Controller { // Note: the SOQL Field list, and Binding Property list have subtle differences as custom properties may be mapped with the JsonProperty attribute to remove __c const string _ContactsPostBinding = "Id,Salutation,FirstName,LastName,MailingStreet,MailingCity,MailingState,MailingPostalCode,MailingCountry,Phone,Email"; // GET: Contacts public async Task<ActionResult> Index() { IEnumerable<Contact> selectedContacts = Enumerable.Empty<Contact>(); try { selectedContacts = await SalesforceService.MakeAuthenticatedClientRequestAsync( async (client) => { QueryResult<Contact> contacts = await client.QueryAsync<Contact>("SELECT Id, Salutation, FirstName, LastName, MailingCity, MailingState, MailingCountry From Contact"); return contacts.records; } ); } catch (Exception e) { this.ViewBag.OperationName = "query Salesforce Contacts"; this.ViewBag.AuthorizationUrl = SalesforceOAuthRedirectHandler.GetAuthorizationUrl(this.Request.Url.ToString()); this.ViewBag.ErrorMessage = e.Message; } if (this.ViewBag.ErrorMessage == "AuthorizationRequired") { return Redirect(this.ViewBag.AuthorizationUrl); } return View(selectedContacts); } public async Task<ActionResult> Details(string id) { IEnumerable<Contact> selectedContacts = Enumerable.Empty<Contact>(); try { selectedContacts = await SalesforceService.MakeAuthenticatedClientRequestAsync( async (client) => { QueryResult<Contact> contacts = await client.QueryAsync<Contact>("SELECT Id, Salutation, FirstName, LastName, MailingStreet, MailingCity, MailingState, MailingPostalCode, MailingCountry, Phone, Email From Contact Where Id = '" + id + "'"); return contacts.records; } ); } catch (Exception e) { this.ViewBag.OperationName = "Salesforce Contacts Details"; this.ViewBag.AuthorizationUrl = SalesforceOAuthRedirectHandler.GetAuthorizationUrl(this.Request.Url.ToString()); this.ViewBag.ErrorMessage = e.Message; } if (this.ViewBag.ErrorMessage == "AuthorizationRequired") { return Redirect(this.ViewBag.AuthorizationUrl); } return View(selectedContacts.FirstOrDefault()); } public async Task<ActionResult> Edit(string id) { IEnumerable<Contact> selectedContacts = Enumerable.Empty<Contact>(); try { selectedContacts = await SalesforceService.MakeAuthenticatedClientRequestAsync( async (client) => { QueryResult<Contact> contacts = await client.QueryAsync<Contact>("SELECT Id, Salutation, FirstName, LastName, MailingStreet, MailingCity, MailingState, MailingPostalCode, MailingCountry, Phone, Email From Contact Where Id= '" + id + "'"); return contacts.records; } ); } catch (Exception e) { this.ViewBag.OperationName = "Edit Salesforce Contacts"; this.ViewBag.AuthorizationUrl = SalesforceOAuthRedirectHandler.GetAuthorizationUrl(this.Request.Url.ToString()); this.ViewBag.ErrorMessage = e.Message; } if (this.ViewBag.ErrorMessage == "AuthorizationRequired") { return Redirect(this.ViewBag.AuthorizationUrl); } return View(selectedContacts.FirstOrDefault()); } [HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> Edit([Bind(Include = _ContactsPostBinding)] Contact contact) { SuccessResponse success = new SuccessResponse(); try { success = await SalesforceService.MakeAuthenticatedClientRequestAsync( async (client) => { success = await client.UpdateAsync("Contact", contact.Id, contact); return success; } ); } catch (Exception e) { this.ViewBag.OperationName = "Edit Salesforce Contact"; this.ViewBag.AuthorizationUrl = SalesforceOAuthRedirectHandler.GetAuthorizationUrl(this.Request.Url.ToString()); this.ViewBag.ErrorMessage = e.Message; } if (this.ViewBag.ErrorMessage == "AuthorizationRequired") { return Redirect(this.ViewBag.AuthorizationUrl); } if (success.success == "true") { return RedirectToAction("Index"); } else { return View(contact); } } public async Task<ActionResult> Delete(string id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } IEnumerable<Contact> selectedContacts = Enumerable.Empty<Contact>(); try { selectedContacts = await SalesforceService.MakeAuthenticatedClientRequestAsync( async (client) => { // Query the properties you'll display for the user to confirm they wish to delete this Contact QueryResult<Contact> contacts = await client.QueryAsync<Contact>(string.Format("SELECT Id, FirstName, LastName, MailingCity, MailingState, MailingCountry From Contact Where Id='{0}'", id)); return contacts.records; } ); } catch (Exception e) { this.ViewBag.OperationName = "query Salesforce Contacts"; this.ViewBag.AuthorizationUrl = SalesforceOAuthRedirectHandler.GetAuthorizationUrl(this.Request.Url.ToString()); this.ViewBag.ErrorMessage = e.Message; } if (this.ViewBag.ErrorMessage == "AuthorizationRequired") { return Redirect(this.ViewBag.AuthorizationUrl); } if (selectedContacts.Count() == 0) { return View(); } else { return View(selectedContacts.FirstOrDefault()); } } [HttpPost, ActionName("Delete")] [ValidateAntiForgeryToken] public async Task<ActionResult> DeleteConfirmed(string id) { bool success = false; try { success = await SalesforceService.MakeAuthenticatedClientRequestAsync( async (client) => { success = await client.DeleteAsync("Contact", id); return success; } ); } catch (Exception e) { this.ViewBag.OperationName = "Delete Salesforce Contacts"; this.ViewBag.AuthorizationUrl = SalesforceOAuthRedirectHandler.GetAuthorizationUrl(this.Request.Url.ToString()); this.ViewBag.ErrorMessage = e.Message; } if (this.ViewBag.ErrorMessage == "AuthorizationRequired") { return Redirect(this.ViewBag.AuthorizationUrl); } if (success) { return RedirectToAction("Index"); } else { return View(); } } public ActionResult Create() { return View(); } [HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> Create([Bind(Include = _ContactsPostBinding)] Contact contact) { String id = String.Empty; try { id = await SalesforceService.MakeAuthenticatedClientRequestAsync( async (client) => { return await client.CreateAsync("Contact", contact); } ); } catch (Exception e) { this.ViewBag.OperationName = "Create Salesforce Contact"; this.ViewBag.AuthorizationUrl = SalesforceOAuthRedirectHandler.GetAuthorizationUrl(this.Request.Url.ToString()); this.ViewBag.ErrorMessage = e.Message; } if (this.ViewBag.ErrorMessage == "AuthorizationRequired") { return Redirect(this.ViewBag.AuthorizationUrl); } if (this.ViewBag.ErrorMessage == null) { return RedirectToAction("Index"); } else { return View(contact); } } } }
Add a partial view to provide login link if not yet authorized
When the list of contacts page loads, it tries to get a list of contact from Salesforce. If this fails due to lack of authentication, we want to provide a link for the user to login.
This is just one way to handle authentication, you may want to authenticate up front, or when the user accesses an authorized resource without making them click a link.
Add Auth Error Page
- Right click on the Views/Shared folder and select Add-->View...
- Name it: _AuthError
- Click the Add button
- Replace the contents of the View with the following code:
@if (ViewBag.ErrorMessage == "AuthorizationRequired") { <p>You have to sign-in to @ViewBag.OperationName. Click <a href="@ViewBag.AuthorizationUrl" title="here">here</a> to sign-in.</p> <p>@ViewBag.ErrorMessage</p> }
Add a partial view to provide error messages returned from the Salesforce API
This partial page will only be visible when there is an unexpected error returned to the user.
Add Error Page
- Right click on the Views/Shared folder and select Add-->View...
- Name it: _UnexpectedError
- Click the Add button
- Replace the contents of the View with the following code:
@if (ViewBag.ErrorMessage != "AuthorizationRequired" && ViewBag.ErrorMessage != null) { <p class="text-danger"> An unexpected error occurred while attempting to @ViewBag.OperationName<br /> <strong>Error: @ViewBag.ErrorMessage</strong> </p> }
Add a Menu item for Salesforce Contacts
- Open Views\Shared\_Layout.cshtml.
- Within the <ul class="nav navbar-nav"> div, add the highlighted code:
<ul class="nav navbar-nav"> <li>@Html.ActionLink("Home", "Index", "Home")</li> <li>@Html.ActionLink("About", "About", "Home")</li> <li>@Html.ActionLink("Contact", "Contact", "Home")</li> <li>@Html.ActionLink("Salesforce Contacts", "Index", "Contacts")</li> </ul>
List View
Using MVC Scaffolding, we can build out the view based on the Model classes scaffolded based on the Salesforce objects.
Note: because we're using a new auth pattern for validating users inline, there are two extra Partial views added to the page.
Copying this code will add these views, however as you move past the sample and use the MVC View Scaffolding, be sure to add these partial views if you continue to use the auth pattern in the controllers.
- Within the Controller.Index Method, right-click and select Add View
- View name: Index
- Template: List
- Model: Contact ([YourProjectName].Models.Salesforce)
- Click Add
- Replace the contents of the razor file with the following code:
- Note: Replace the @model namespace WebApplication1 with the namespace of your project
@model IEnumerable<WebApplication1.Models.Salesforce.Contact> @{ ViewBag.Title = "Contacts"; } <h2>Contacts</h2> <p> @Html.ActionLink("Create New", "Create") </p> <table class="table"> <tr> <th> @Html.DisplayNameFor(model => model.Salutation) </th> <th> @Html.DisplayNameFor(model => model.FirstName) </th> <th> @Html.DisplayNameFor(model => model.LastName) </th> <th> @Html.DisplayNameFor(model => model.MailingCity) </th> <th> @Html.DisplayNameFor(model => model.MailingState) </th> <th> @Html.DisplayNameFor(model => model.MailingCountry) </th> <th></th> </tr> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.Salutation) </td> <td> @Html.DisplayFor(modelItem => item.FirstName) </td> <td> @Html.DisplayFor(modelItem => item.LastName) </td> <td> @Html.DisplayFor(modelItem => item.MailingCity) </td> <td> @Html.DisplayFor(modelItem => item.MailingState) </td> <td> @Html.DisplayFor(modelItem => item.MailingCountry) </td> <td> @Html.ActionLink("Edit", "Edit", new { id = item.Id }) | @Html.ActionLink("Details", "Details", new { id = item.Id }) | @Html.ActionLink("Delete", "Delete", new { id = item.Id }) </td> </tr> } </table>
Details View
- Within the Controller.Details Method, right-click and select Add View
- View name: Details
- Template: Details
- Model: Contact ([YourProjectName].Models.Salesforce)
- Click Add
- Replace the contents of the razor file with the following code:
- Replace the @model namespace WebApplication1 with the namespace of your project
@model WebApplication1.Models.Salesforce.Contact @{ ViewBag.Title = "View"; } <h2>View</h2> <div> <h4>Contact</h4> <hr /> <dl class="dl-horizontal"> <dt> @Html.DisplayNameFor(model => model.Salutation) </dt> <dd> @Html.DisplayFor(model => model.Salutation) </dd> <dt> @Html.DisplayNameFor(model => model.FirstName) </dt> <dd> @Html.DisplayFor(model => model.FirstName) </dd> <dt> @Html.DisplayNameFor(model => model.LastName) </dt> <dd> @Html.DisplayFor(model => model.LastName) </dd> <dt> @Html.DisplayNameFor(model => model.MailingStreet) </dt> <dd> @Html.DisplayFor(model => model.MailingStreet) </dd> <dt> @Html.DisplayNameFor(model => model.MailingCity) </dt> <dd> @Html.DisplayFor(model => model.MailingCity) </dd> <dt> @Html.DisplayNameFor(model => model.MailingState) </dt> <dd> @Html.DisplayFor(model => model.MailingState) </dd> <dt> @Html.DisplayNameFor(model => model.MailingPostalCode) </dt> <dd> @Html.DisplayFor(model => model.MailingPostalCode) </dd> <dt> @Html.DisplayNameFor(model => model.MailingCountry) </dt> <dd> @Html.DisplayFor(model => model.MailingCountry) </dd> <dt> @Html.DisplayNameFor(model => model.Phone) </dt> <dd> @Html.DisplayFor(model => model.Phone) </dd> <dt> @Html.DisplayNameFor(model => model.Email) </dt> <dd> @Html.DisplayFor(model => model.Email) </dd> </dl> </div> <p> @Html.ActionLink("Edit", "Edit", new { id = Model.Id }) | @Html.ActionLink("Back to List", "Index") </p>
Edit View
- Within the Controller.Edit Method, right-click and select Add View
- View name: Edit
- Template: Edit
- Model: Contact ([YourProjectName].Models.Salesforce)
- Click Add
- Replace the contents of the razor file with the following code:
- Replace the @model namespace WebApplication1 with the namespace of your project
@model WebApplication1.Models.Salesforce.Contact @{ ViewBag.Title = "Edit"; } <h2>Edit</h2> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>Contact</h4> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) @Html.HiddenFor(model => model.Id) <div class="form-group"> @Html.LabelFor(model => model.Salutation, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Salutation, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Salutation, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.FirstName, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.FirstName, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.LastName, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.LastName, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.LastName, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.MailingStreet, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.MailingStreet, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.MailingStreet, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.MailingCity, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.MailingCity, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.MailingCity, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.MailingState, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.MailingState, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.MailingState, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.MailingPostalCode, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.MailingPostalCode, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.MailingPostalCode, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.MailingCountry, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.MailingCountry, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.MailingCountry, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Phone, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Phone, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Phone, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Save" class="btn btn-default" /> </div> </div> </div> } <div> @Html.ActionLink("Back to List", "Index") </div> @section Scripts { @Scripts.Render("~/bundles/jqueryval") }
Delete View
- Within the Controller.Delete Method, right-click and select Add View
- View name: Delete
- Template: Delete
- Model: Contact ([YourProjectName].Models.Salesforce)
- Click Add
- Replace the contents of the razor file with the following code:
- Replace the @model namespace WebApplication1 with the namespace of your project
@model WebApplication1.Models.Salesforce.Contact @{ ViewBag.Title = "Delete"; } <h2>Delete</h2> <h3>Are you sure you want to delete this?</h3> <div> <h4>Contact</h4> <hr /> <dl class="dl-horizontal"> <dt> @Html.DisplayNameFor(model => model.FirstName) </dt> <dd> @Html.DisplayFor(model => model.FirstName) </dd> <dt> @Html.DisplayNameFor(model => model.LastName) </dt> <dd> @Html.DisplayFor(model => model.LastName) </dd> <dt> @Html.DisplayNameFor(model => model.MailingCity) </dt> <dd> @Html.DisplayFor(model => model.MailingCity) </dd> <dt> @Html.DisplayNameFor(model => model.MailingState) </dt> <dd> @Html.DisplayFor(model => model.MailingState) </dd> <dt> @Html.DisplayNameFor(model => model.MailingCountry) </dt> <dd> @Html.DisplayFor(model => model.MailingCountry) </dd> </dl> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div class="form-actions no-color"> <input type="submit" value="Delete" class="btn btn-default" /> | @Html.ActionLink("Back to List", "Index") </div> } </div>
Create View
- Within the Controller.Create Method, right-click and select Add View
- View name: Create
- Template: Create
- Model: Contact ([YourProjectName].Models.Salesforce)
- Click Add
- Replace the contents of the razor file with the following code:
- Replace the @model namespace WebApplication1 with the namespace of your project
@model WebApplication1.Models.Salesforce.Contact @{ ViewBag.Title = "Create"; } <h2>Create</h2> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>Contact</h4> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div class="form-group"> @Html.LabelFor(model => model.Salutation, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Salutation, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Salutation, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.FirstName, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.FirstName, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.LastName, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.LastName, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.LastName, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.MailingStreet, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.MailingStreet, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.MailingStreet, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.MailingCity, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.MailingCity, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.MailingCity, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.MailingState, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.MailingState, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.MailingState, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.MailingPostalCode, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.MailingPostalCode, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.MailingPostalCode, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.MailingCountry, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.MailingCountry, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.MailingCountry, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Phone, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Phone, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Phone, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Create" class="btn btn-default" /> </div> </div> </div> } <div> @Html.ActionLink("Back to List", "Index") </div> @section Scripts { @Scripts.Render("~/bundles/jqueryval") }
Run the app
- Close all files before debugging to launch the home page of your website
- Hit F5 or CTRL+F5 to start your app
- Click the Salesforce Contacts link
- Login with your Salesforce account
- Create a new contact
- Click the details on the newly created contact
- Edit it, changing the name
- Delete the contact
Voila, you've just walked through creating a Salesforce contact in an ASP.net MVC application, using the Salesforce REST APIs and the Force.com-Toolkit-for-NET
Having some problems? Here's some quick tips we've found along the way
- Failures when completing the wizard? - Check the output window, as we log progress, and timings for the steps being completed.
Creating a Console app to view, edit and delete Salesforce Contacts
This walkthrough will help you create a basic Console App that utilizes a System Account based on the Username/Password authentication strategy, performing basic CRUD operations.
When creating a Console App, be sure to choose the Service Account-Username/Password runtime authentication strategy.
Note: This sample assumes you selected the Contact Salesforce Object in the scaffolding step of the wizard.
Using a Service Account with Username/Password authentication
Developers will often write a Console application for initial testing, prototyping, or as a headless application run as a scheduled job. The headless app can either be scheduled with Windows Scheduler, or you may choose to create a Windows Service project.
In console applications, there's no web page, httpContext, or UI to speak of. In this sample, we'll use the Username/Password authentication strategy to authenticate against Salesforce without user interaction.
- Open app.config and set the following values.
- ConsumerSecret is found using the information in Next Steps above.
- Username is the user the console will use at runtime. Typically a service account with limited permissions, but for demos, your account can be used.
- SecurityToken is retrieved from Salesforce Setup. See more info for retrieving the securitytoken, or Set Trusted IP Ranges for Your Organization to avoid having to use the user security token. If you've setup a trusted network, you can either remove the config entry or leave it as OptionalValue
<add key="Salesforce:ConsumerKey" value="populated by the Connected Service Configuration" /> <add key="Salesforce:ConsumerSecret" value="RequiredValue" /> <add key="Salesforce:Username" value="RequiredValue" /> <add key="Salesforce:Password" value="RequiredValue" /> <add key="Salesforce:SecurityToken" value="OptionalValue" />
Add using statements
- Open Program.cs
- Include the following using statements:
using Salesforce.Common; using Salesforce.Common.Models; using Salesforce.Force;
Add a Query Contacts Method
- In Program.cs, below the Main(string[] args) method, add the following code:
private static async Task QueryContacts() { Console.WriteLine("Authenticating with Salesforce, using the Username/Password authentication strategy"); ForceClient client = await Salesforce.SalesforceService.GetUserNamePasswordForceClientAsync(); Console.WriteLine("Connected to Salesforce"); Console.WriteLine("Get a list of Contacts"); // a collection to hold the results as we get multile resultsets var contacts = new List<Models.Salesforce.Contact>(); var results = await client.QueryAsync<Models.Salesforce.Contact>( "SELECT Id, FirstName, LastName, Email FROM Contact ORDER BY LastName, FirstName"); var totalSize = 0; totalSize = results.totalSize; Console.WriteLine(string.Format("Queried {0} records.", totalSize)); contacts.AddRange(results.records); var nextRecordsUrl = results.nextRecordsUrl; while (!string.IsNullOrEmpty(nextRecordsUrl)) { Console.WriteLine("Found nextRecordsUrl."); // Query the next ResultSet QueryResult<Models.Salesforce.Contact> continuationResults = await client.QueryContinuationAsync<Models.Salesforce.Contact>(nextRecordsUrl); Console.WriteLine(string.Format("Queried an additional {0} records.", continuationResults.totalSize)); // Add the resultSet to our parent collection contacts.AddRange(continuationResults.records); //pass nextRecordsUrl back to client.QueryAsync to request next set of records nextRecordsUrl = continuationResults.nextRecordsUrl; } Console.WriteLine(string.Format("Retrieved accounts {0}, expected size = {1}", totalSize, contacts.Count())); Console.WriteLine(); Console.BackgroundColor = ConsoleColor.DarkGreen; Console.ForegroundColor = ConsoleColor.Black; Console.WriteLine("Print out the list of {0} contacts? (y/n)", contacts.Count()); if (Console.ReadKey(true).Key == ConsoleKey.Y) { // Format a console "table" with headings Console.ResetColor(); Console.BackgroundColor = ConsoleColor.Blue; Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("{0}{1}{2}", "Last Name".PadRight(20), "First Name".PadRight(15), "Email".PadRight(30)); Console.BackgroundColor = ConsoleColor.White; Console.ForegroundColor = ConsoleColor.Blue; foreach (var contact in contacts) { Console.WriteLine("{0}{1}{2}", contact.LastName.PadRight(20), contact.FirstName.PadRight(15), (contact.Email + "").PadRight(30).Substring(0, 30)); } } Console.ResetColor(); }
Add Create, Update and Delete functionality
- In Program.cs, below the QueryContacts() method, add the following code:
private static async Task CreateUpdateDeleteContact() { Console.WriteLine("Authenticating with Salesforce, using the Username/Password authentication strategy"); ForceClient client = await Salesforce.SalesforceService.GetUserNamePasswordForceClientAsync(); Console.WriteLine("Connected to Salesforce"); // Create a sample record Console.WriteLine("Creating test Contact."); var contact = new Models.Salesforce.Contact{ FirstName = "Freddy", LastName="Krueger", MailingStreet="Elm Street" }; contact.Id = await client.CreateAsync("Contact", contact); if (contact.Id == null) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("--> Failed to create test Contact."); Console.ResetColor(); return; } else { Console.ForegroundColor = ConsoleColor.DarkGreen; Console.WriteLine("--> Successfully created test record."); Console.ResetColor(); } Console.WriteLine(); // Update the sample record // Shows that annonymous types can be used as well Console.WriteLine("Updating test record."); var updateSuccess = await client.UpdateAsync("Contact", contact.Id, new { MailingCity = "Some City" }); if (updateSuccess.success != "true") { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("--> Failed to update test record!"); return; } else { Console.ForegroundColor = ConsoleColor.DarkGreen; Console.WriteLine("--> Successfully created test record."); Console.ResetColor(); } Console.WriteLine(); // Retrieve the sample record // How to query with a WHERE clause Console.WriteLine("Retrieving the record by ID."); var result= await client.QueryAsync<Models.Salesforce.Contact>( string.Format("SELECT FirstName, LastName, Email, MailingStreet, MailingCity FROM Contact WHERE Id = '{0}'", contact.Id)); if (result.records.Count() == 0) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("--> Failed to retrieve the record by ID!"); Console.ResetColor(); return; } else { // Use FirstOrDefault extension method to get a single item from QueryAsync // note QueryResult returns a result objects, with records following the common REST programming model contact = result.records.FirstOrDefault(); Console.ForegroundColor = ConsoleColor.DarkGreen; Console.WriteLine(string.Format("--> Successfully found {0}, {1}", contact.LastName, contact.FirstName)); Console.ResetColor(); } Console.WriteLine(); // Query for record by name Console.WriteLine("Querying the Contacts by FirstName."); var searchParam = "Fred"; var parameterizedResults = await client.QueryAsync<Models.Salesforce.Contact>( string.Format("SELECT Id, FirstName, LastName FROM Contact WHERE FirstName Like '%{0}%'", searchParam)); // Get the contact= parameterizedResults.records.FirstOrDefault(); if (contact == null) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(string.Format("--> Failed to find any Contacts named {0}", searchParam)); Console.ResetColor(); return; } else { Console.ForegroundColor = ConsoleColor.DarkGreen; Console.WriteLine(string.Format("--> Found {0} records named {1}", parameterizedResults.records.Count(), searchParam)); Console.ResetColor(); } Console.WriteLine(); // Delete account Console.WriteLine("Deleting the record by ID."); var deleteSuccess = await client.DeleteAsync("Contact", contact.Id); if (!deleteSuccess) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("--> Failed to delete the record by ID!"); Console.ResetColor(); return; } else { Console.ForegroundColor = ConsoleColor.DarkGreen; Console.WriteLine("--> Deleted the record by ID."); Console.ResetColor(); } Console.WriteLine(); }
Add a Query with dynamic types
- In Program.cs, below the CreateUpdateDeleteContact() method, add the following code:
private static async Task QueryWithDynamic() { Console.WriteLine("Authenticating with Salesforce, using the Username/Password authentication strategy"); ForceClient client = await Salesforce.SalesforceService.GetUserNamePasswordForceClientAsync(); Console.WriteLine("Connected to Salesforce"); // Selecting multiple accounts into a dynamic Console.WriteLine("Querying multiple records."); var dynamicAccounts = await client.QueryAsync<dynamic>("SELECT ID, Name FROM Account LIMIT 10"); // Format a console "table" with headings Console.ResetColor(); Console.BackgroundColor = ConsoleColor.Blue; Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("{0}{1}", "Id".PadRight(20), "Name".PadRight(30)); Console.BackgroundColor = ConsoleColor.White; Console.ForegroundColor = ConsoleColor.Blue; foreach (dynamic acct in dynamicAccounts.records) { Console.WriteLine("{0}{1}", ((string)(acct.Id)).PadRight(20), ((string)(acct.Name)).PadRight(30).Substring(0, 30)); } Console.ResetColor(); }
Call the Sample Methods in Sequence
- Replace Main() method, with the following code:
- Hit F5 to test your console app.
static void Main(string[] args) { try { var queryContacts= QueryContacts(); queryContacts.Wait(); var createUpdateDeleteContact = CreateUpdateDeleteContact(); createUpdateDeleteContact.Wait(); var queryWithDynamic = QueryWithDynamic(); queryWithDynamic.Wait(); } catch (Exception e) { // Drill into the inner exception as the parent exception is related to the Task Console.ResetColor(); // Show errors as red Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(e.Message); var innerException = e.InnerException; while (innerException != null) { Console.WriteLine(innerException.Message); innerException = innerException.InnerException; } } Console.WriteLine(); Console.BackgroundColor = ConsoleColor.DarkGreen; Console.ForegroundColor = ConsoleColor.Black; Console.WriteLine("Press ENTER to Exit"); Console.ReadLine(); }