|
|
|
Eric Shupps BinaryWave
611 S. Main St., Suite 400
Grapevine , TX , 76051 USA
|
|
| The great thing about SharePoint Enterprise Search is that once it's up and running it mostly just works. Except, of course, when it doesn't. Recently I was troubleshooting a farm in which People search stopped returning any results. In this environment, the main OU containing user accounts had been moved and a new profile import run to re-populate the profile database. The import was successful but the changes required a full crawl of the content source which had been working just fine up until that point. After the next crawl completed all queries against the People scope returned an empty result set.
After confirming that none of the permissions had changed, and that the default crawl account still had access to the User Profile service app and all web applications, another full was run without success. It was during this run that the crawl log displayed the following error for the primary web application (which had probably been there all along but was buried beneath a number of other content-related crawl errors):
An unrecognized HTTP response was received when attempting to crawl this item. Verify whether the item can be accessed using your browser.
A bit of research indicated that this is usually due to insufficient permissions – either the crawl account does not have Full Read permissions set in the web application User Policy or it hasn't been granted the "Retrieve People Data for Search Crawlers" right for the user profile service application. But in this case both sets of permissions were valid. An attempt to connect to the search crawl web service (/_vti_bin/spscrawl.asmx) confirmed this but also revealed something interesting – all HTTP requests were being redirected to HTTPS. Another look at the Content Source in Search Administration showed that the primary web application was, in fact, set up to crawl content via an HTTPS URL.
The SharePoint People Search mechanism requires a specific URL format within a Content Source, one that begins with "sps3://" instead of the usual "http://". The site cannot actually be browsed via this URL; instead, it's merely a connector reference that the search crawler associates it with the proper URI under the covers. When a web application is secured via SSL the protocol portion of the URL has to be changed from "sps3://" to "sps3s://" – the "s" letting the crawler know it should use HTTPS instead of HTTP.
Specifying an SSL target for the People search content source
Changing the URL value in the Content Source from "sps3://" to "sps3s://" resulted in a successful crawl and People search started returning results at the conclusion of the next full crawl. So if any of the web applications in your farm use HTTPS by default, be sure to check the Content Source settings to be sure that the search crawler can properly access them.
SharePoint is
Talking. Are you
Listening?
|
| Keenan Newton and Rob Lefferts from the SharePoint product team at Microsoft will be presenting a webcast on Monday, May 20, 2013, at 9:00am US Pacific Time. They'll be covering the thorny topic of migrating legacy full-trust solutions to the SharePoint 2013 app model - something a lot of developers and business analysts will need to be thinking about as the shift away from on-box compiled code continues. If you are currently developing SharePoint solutions and your organization is considering an on-premise upgrade or a move to Office365, you need to attend this webcast. Link and registration info below.
Reimagine SharePoint Development: A better way to customize SharePoint
Event ID: 1032553224
| Language(s): English. |
| Product(s): Microsoft Office 365 and Microsoft SharePoint Server 2013. |
Audience(s): Developer Generalist and IT Decision Maker.
Date: 5/20/2013 Time: 9:00AM US PST |
Join Senior Product Marketing Manager, Keenan Newton, and special guest Partner Director of Apps Program Management, Robert Lefferts, as we kick off our new site centered around migrating SharePoint solutions to apps. We will discuss the history of SharePoint customizations and where the SharePoint development platform is going. We will also highlight the benefits of the cloud app model and answer any questions that you may have.
The webcast will be broadcast on Channel 9. This will be the first live webcast in a series of webcasts focused on migrating SharePoint solutions to apps. These webcasts along with soon coming content on
|
|
| The following article is the third in a three part series on data model patterns for SharePoint 2013 apps.
In Part One of this series we reviewed the basic parameters for a data model pattern that can be used for building SharePoint 2013 apps and defined the process for creating a base data layer using the ADO.NET Entity Framework. In Part Two we explored the use of WCF to create a middle-tier service layer. In this post we will discuss the final piece of the puzzle – binding data to presentation objects in the user interface using JavaScript, JQuery and AJAX.
Overview
For more than a decade server-side programming languages served as the primary means for created web applications. Java and .NET accounted for the bulk of all web programming with client-side scripting languages used mostly as add-ons or enhancements. Occasionally, a creative developer would put together a pure client-side application but these were few and far between and certainly not well represented in any type of business or enterprise application. As client technologies evolved and more attention was given to the creation of libraries to expand their capabilities, these languages, led by JavaScript (or, if you are a die-hard standards conformist, ECMAScript), have taken center stage in what are referred to as "modern", "fluid", or "responsive" applications. These types of applications are defined by their near-complete lack of server-side code and the absence of visible round trips to the server (evidenced primarily by the dreaded screen-wiping postback mechanism). Adoption of client-centric web applications has been spurred in no small part by plugins such as JQuery, which obfuscate many of the tedious tasks associated with programming in a loosely-typed, uncompiled script language and give even raw beginners the ability to quickly create dynamic applications.
This programming model has its obvious limitations as part of an overall pattern that involves structured data access. While an elegant data layer built that interacts with a SQL database is directly accessible in server-side code, client-script running in the web browser has no way of interfacing with it. Furthermore, the lack of strongly-typed objects means that there is no way to directly bind UI elements to objects in the data layer. There needs to be a way for the presentation tier to talk to the data tier in a structured manner using a language that both understand. This is where AJAX and JSON come in. The former provides a mechanism for handling asynchronous calls over HTTP to remote services while the latter defines a data structure for request/response payloads (XML does the exact same thing but is much more cumbersome than JSON, which was designed as a lighter-weight alternative to endless strings of text featuring angle brackets and namespaces). In practical terms, the remote service accepts a request from the client, who specifies that JSON as the message format, and returns a response in the same format.
Once the communication methods and data structure have been identified the client application then needs a way to initiate communication and deal with the result. JQuery makes this easy, as it provides simple methods for executing an asynchronous request and parsing the results. It also provides the mechanism for binding data to control elements or otherwise updating the UI based on the results of the request/response procedure. This is all accomplished in series of JavaScript functions that are invoked by various events, such as page loads, window operations, clicks, keystrokes and the like.
In context of the overall pattern, the presentation tier elements are all related to the parsing of data as defined by the WCF service, which describes the uniform message structure and payload formatting, and the manner in which the service is called. There are no requirements for specific UI components, script behaviors, variable declaration or even function structure. All that is required are a properly formatted request and the ability to parse JSON contained within the response.
Implementation
The presentation tier is comprised entirely of JavaScript methods. Functions handle application events and features of the JQuery library used to request data and update the UI. Each call to the WCF service is made asynchronously with data operations contained within success and failure handlers. The data is then bound to controls or otherwise acted upon using JQuery and base JavaScript functions. The most important factors to consider within the presentation tier implementation are 1) Correct instantiation and parsing of JSON objects, 2) Managing the sequence of asynchronous event chains, and 3) Proper request formatting.
1 |
Create UI Elements
Begin with a set of UI elements that data will be bound to. For purposes of this demonstration, the user will type a numeric value into a text box, click a button and retrieve full contact details from the database. The simplified markup for this scenario is as follows:
<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="BWAppDataModel._Default" %>
<asp:Content runat="server" ID="FeaturedContent" ContentPlaceHolderID="FeaturedContent">
</asp:Content>
<asp:Content runat="server" ID="BodyContent" ContentPlaceHolderID="MainContent">
<div id="userInput" class="inputForm">
<input id="inputUserId" class="inputField" /> <button id="buttonSubmit" class="button" onclick="getUserInfo();return false;">Submit</button>
</div>
<div id="userDetails" class="detailForm">
<div class="detailHeader">User Information</div>
<div class="table">
<div class="row">
<div class="cellLabel">User Name:</div>
<div id="fieldUserName" class="cellText"></div>
</div>
<div class="row">
<div class="cellLabel">Login Name:</div>
<div id="fieldLoginName" class="cellText"></div>
</div>
<div class="row">
<div class="cellLabel">Email Address:</div>
<div id="fieldEmail" class="cellText"></div>
</div>
<div class="row">
<div class="cellLabel">Phone:</div>
<div id="fieldPhone" class="cellText"></div>
</div>
<div class="row">
<div class="cellLabel">Address:</div>
<div id="fieldAddress" class="cellText"></div>
</div>
<div class="row">
<div class="cellLabel">City:</div>
<div id="fieldCity" class="cellText"></div>
</div>
<div class="row">
<div class="cellLabel">State:</div>
<div id="fieldState" class="cellText"></div>
</div>
<div class="row">
<div class="cellLabel">Zip Code:</div>
<div id="fieldZip" class="cellText"></div>
</div>
</div>
</div>
</asp:Content>
NOTE: An ASPX page was used for this demonstration as one is created by default when the Azure Web Role project is selected in Visual Studio 2012; however, a plain HTML page running on any web server platform works just as well. |
2 |
Call the WCF Service
Setup for the script elements is done when the page initially loads using the $(document).ready(function () {}); method of JQuery. In the following initialization function, a global variable for the WCF service is assigned and some AJAX parameters set to permit cross-domain calls (even though they won't be used in this context as the service is in the same domain as the web application).
function initializePage() {
svcUrl = window.location.href;
if (svcUrl.indexOf("?") != -1)
svcUrl = svcUrl.substr(0, svcUrl.indexOf("?"));
svcUrl = svcUrl.substr(0, svcUrl.lastIndexOf("/")) + '/AppDataService.svc/';
$.ajaxSetup({
cache: false,
crossDomain: true
});
$.support.cors = true;
}
The getUserInfo() function assigned to the onClick() event of the "buttonSubmit" button first gets the id value entered by the user then constructs an asynchronous AJAX call to the web service URL, specifying the operation contract, query string arguments, data type (JSON), and request type (GET or POST). Two event handlers are also assigned, one which fires if the request succeeds and the other if it fails.
function getUserInfo() {
var userId = $("#inputUserId").val();
$.ajax({
cache: false,
url: svcUrl + "GetUser?UserId=" + userId,
dataType: "json",
type: "GET",
success: function (result) {
},
error: function (result) {
}
});
}
|
3 |
Parse the Response
The response received from the WCF service will be handled by either the success or error methods. Since the operation contracts in the WCF service (refer to Part Two of this series) use a consistent format, every call can be handled in the same manner. First, the JSON string is parsed using the $.parseJSON method. The first element in the array is always "d" so the object notation for the parsing operation is "result.d" ('result' being the payload of the success function). This is assigned to a variable and then checked for the existence of an "Error" value at the first array position. If found, failure logic is invoked (in this case, an alert with the error message as provided for in the Catch block of the operation contract). If no "Error" value is found, the "user" object (which is the variable in the operation contract containing the result of the entity function execution) is assigned to a variable.
function getUserInfo() {
var userId = $("#inputUserId").val();
$.ajax({
cache: false,
url: svcUrl + "GetUser?UserId=" + userId,
dataType: "json",
type: "GET",
success: function (result) {
var objData = $.parseJSON(result.d);
if (objData.Error) {
var r = $.parseJSON(result.d);
alert(r.Error);
} else {
var data = objData.user
}
},
error: function (result) {
var r = $.parseJSON(result.d);
alert(r.Error);
}
});
}
It is important to note that the request/response process is asynchronous; that is, the thread will continue executing while the operation is taking place. If there are other operations that are dependent upon the success or failure of the AJAX request (such as binding data to a control) then they must be called in either the "success" or "error" event handlers; otherwise, the operations will execute before the response is received from the service call. Asynchronous event chaining can be one of the most difficult aspects of JavaScript programming as there is no way to ever know how long a request will take to complete; the more complex the application, the more chained events, and the harder the overall process is to follow. Visual Studio 2012 includes some enhancements for JavaScript debugging to aid in this task. |
4 |
Bind Data to UI Elements
Once the response has been received and parsed, the data can then be bound to UI elements using JQuery. The values are accessed by calling the variable the object was assigned to in the "success" handler and specifying the desired property (such as FirstName or Email) as a child element.
function getUserInfo() {
var userId = $("#inputUserId").val();
$.ajax({
cache: false,
url: svcUrl + "GetUser?UserId=" + userId,
dataType: "json",
type: "GET",
success: function (result) {
var objData = $.parseJSON(result.d);
if (objData.Error) {
var r = $.parseJSON(result.d);
alert(r.Error);
} else {
var data = objData.user;
$("#fieldUserName").html(data.FirstName + " " + data.LastName);
$("#fieldLoginName").html(data.LoginName);
$("#fieldEmail").html(data.Email);
$("#fieldPhone").html(data.Phone1);
$("#fieldAddress").html(data.Address1);
$("#fieldCity").html(data.City);
$("#fieldState").html(data.State);
$("#fieldZip").html(data.Zip);
$("#userDetails").show();
}
},
error: function (result) {
var r = $.parseJSON(result.d);
alert(r.Error);
}
});
}
The final result of the sample application after the data has been processed and bound to the UI elements:
|
4 |
POST Operations
Sending data to the WCF service using a complex object in a POST operation requires slightly different formatting but the process is essentially the same. First, the object must be created as an array that can be serialized into a JSON string. The first element in the array must be the name of the variable specified in the operation contract parameter; in this case, the contract is UpsertUser(DataContracts.UserInfoObject User) so the array must start with "User:". A secondary child array is then created to hold each of the object properties. These must have the same name and appear in the same order as the DataMember values specified in the DataContract for the specified object.
function updateUserInfo() {
var _object = {
User: {
UserId: $("#inputUserId").val(),
LoginName: $("#fieldLoginName").html(),
FirstName: $("#fieldUserName").html().split(" ")[0],
LastName: $("#fieldUserName").html().split(" ")[1],
Email: $("#fieldEmail").html(),
Phone1: $("#inputPhone").html(),
Phone2: '',
Address1: $("#fieldAddress").html(),
Address2: '',
City: $("#fieldCity").html(),
State: $("#fieldState").html(),
Zip: $("#fieldZip").html(),
Country: 'US',
Active: 'True',
IsEngineer: 'True',
ImageUrl: ''
}
};
}
The object can then be serialized and sent to the service. The AJAX call syntax is similar to that used in a GET operation; however, the dataType is different (POST) and two additional parameters are required – "data" and "contentType". The "data" parameter specifies the serialized object and the "contentType" informs the server what MIME type to use. When the WCF service receives the object, it will map it to the data contract and, if the name, properties, and order are all correct, the object may then be used to call an entity function or run some other code. Since this particular operation does not return any data, the response is checked for an Error object and the user alerted if the request succeeded or failed.
function updateUserInfo() {
var _object = {
User: {
UserId: $("#inputUserId").val(),
LoginName: $("#fieldLoginName").html(),
FirstName: $("#fieldUserName").html().split(" ")[0],
LastName: $("#fieldUserName").html().split(" ")[1],
Email: $("#fieldEmail").html(),
Phone1: $("#inputPhone").html(),
Phone2: '',
Address1: $("#fieldAddress").html(),
Address2: '',
City: $("#fieldCity").html(),
State: $("#fieldState").html(),
Zip: $("#fieldZip").html(),
Country: 'US',
Active: 'True',
IsEngineer: 'True',
ImageUrl: ''
}
};
$.ajax({
cache: false,
url: svcUrl + "UpsertUser",
data: JSON.stringify(_object),
dataType: "json",
type: "POST",
contentType: 'application/json; charset=utf-8',
success: function (result) {
var objData = $.parseJSON(result.d);
if (objData.Error) {
var r = $.parseJSON(result.d);
alert(r.Error);
} else {
alert("Update Succeeded");
}
},
error: function (result) {
var r = $.parseJSON(result.d);
alert(r.Error);
}
});
} |
Once the presentation tier is in place the pattern is complete. All subsequent data access functions on the client follow the same structure – an AJAX call to the service endpoint using either a GET or POST operation and a response handler to parse the JSON result. As with the WCF operation contracts, the process is as simple as copy > paste > modify. With a consistent request/response mechanism in place the code is easy to test and debug. It uses common JQuery components to eliminate any reliance on platform-specific implementations (such as the ASP.NET Script Manager). Perhaps most importantly, it allows the developer to concentrate on application design rather than the minutiae of SOAP messages and XPATH expressions.
The overall pattern was created to satisfy the following objectives: 1) Provide a consistent methodology for CRUD operations from client-side script to/from a remote data repository, 2) Define a middle-tier service layer with uniform object and method structures, and 3) Minimize the amount of manual coding required to create and manage core data access classes. As implemented, it meets these requirements and reduces the overhead associated with consuming remote data sources from JavaScript. With each operation essentially being a copy and paste procedure, new apps can be deployed in a very short period of time and, if used consistently on multiple projects, greatly decrease time spent on troubleshooting and maintenance. By exposing backend data sources as serialized JSON objects, the data model is compatible with most JQuery plugins and extensions. It also facilitates rapid creation of platform-specific mobile applications and is cross-browser compatible. While the pattern was originally intended for SharePoint and Office365 apps, it works equally well with any HTML + JavaScript application.
Naturally, there are many opportunities for improvement in the pattern. It would be ideal to have a Visual Studio plugin that would automatically generate data and operation contracts based on an entity model. An abstracted JavaScript library that exposed a few simple commands and did all the request setup and response parsing behind the scenes would also be helpful (so the contents of the $.ajax({}) call could be reduced to a single line like "var user = $.callService(GetUser)" or something similar). Perhaps some enterprising members of the community will take on those challenges. If nothing else, my hope is that developers who know SharePoint but don't know JavaScript (or even the other way around) are able to use the pattern to rapidly create apps with real business value for the marketplace and internal corporate catalogs.
For those who wish to delve into the sample application used in this series, the full Visual Studio 2012 solution can be downloaded here. Any questions regarding the pattern can be posted as comments to this blog or on Twitter using #spappdatamodel or @eshupps.
Additional References
An Introduction to JavaScript Object Notation (JSON) in JavaScript and .NET
AJAX with JQuery
Parsing JSON Results
Getting Started with JQuery
Introducing JSON |
| The following article is the second in a three part series on data model patterns for SharePoint 2013 apps.
In Part One of this series I set out the basic parameters for a data model pattern that can be used for building SharePoint 2013 apps and defined the process for creating a base data layer using the ADO.NET Entity Framework. In this post we will explore the second part of the pattern – building a middle-tier service layer with WCF.
Overview
The Entity Framework is a great addition to Visual Studio that increases developer efficiency by automatically generating data access classes, objects and functions. On its own, however, it isn't of much use as part of a de-coupled, client-side programming model. While classic server-side applications can directly access the various entities and objects in the framework, a rich client app that uses HTML and JavaScript to deliver a modern, response UI without postbacks doesn't have the same capabilities. The same can be said for mobile platform-dependent apps like those for IOS and Android. This necessitates the creation of a service-oriented middle tier that can be accessed by any client application using a standardized data format and request/response mechanism.
For web developers working within the Microsoft stack, and specifically for those who write .NET applications, the best option for building SOA solutions is Windows Communication Foundation. WCF offers a loosely-coupled service architecture with interoperability across multiple platforms and is deeply integrated into the Visual Studio toolset with a great deal of documentation, community support, guidance and best practices freely available.
WCF services expose a set of endpoints that can be accessed via standardized web protocols. Methods are provided as Operation Contracts, in which data objects are exposed as Data Contracts comprised of strongly-typed Data Members. In the context of the pattern being described here, the WCF service accepts asynchronous messages from the client formatted using JavaScript Object Notation (JSON), calls functions within the Entity Framework to pass data to and from SQL server, then returns data to the client as a JSON string. The response is then parsed and bound to UI objects using JQuery. The service can also be used to communicate with SharePoint via the client object model, handle authorization tasks in situations where the client is unable to do so (such as cross-domain calls to Azure ACS for OAuth token processing in Office365) or run any necessary server-side code. As such, it fulfills the role of data broker in the pattern, acting as an agent to handle CRUD operations and signaling messages.
Implementation
The WCF service layer is comprised of several components. First, the service itself, and its associated configuration parameters and bindings, are created within the web application solution (thereby circumventing any cross-domain issues when the service is called by a JavaScript client). A class or classes containing data contracts is then created to map incoming messages to data objects when parameterized operations are required. Finally, a set of operation contracts are defined that can be directly accessed as a URL in an HTTP POST operation and, where necessary, receive complex serialized objects (in cases where no parameters are required the operation contract simply responds to the incoming request on the defined URL). Most importantly, the operation contracts must be able to accept requests and return responses in JSON format so the data can easily be manipulated by a JavaScript client.
NOTE: JSON is not, strictly speaking, required as part of the pattern. XML can also be used to exchange messages; however, JSON is lighter weight and is being widely adopted as the favored approach in many client-side applications.
1 |
Create a WCF Service
Right-click on the web application project in the solution and select Add > New Item > Web > WCF Data Service
Add ServiceContract and compatibility attributes to the service class, along with the requisite using statements, and remove the auto-generated DataService type inheritance:
using System;
using System.IO;
using System.Net;
using System.Web;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Text;
using System.Data.Objects;
using System.Web.Script.Serialization;
using System.Collections.Specialized;
namespace BWAppDataModel
{
[ServiceContract(Namespace = "BWAppDataModel")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class AppDataService
{
}
}
|
2 |
Edit Configuration Settings
Edit the system.ServiceModel node in web.config to specify endpoint behaviors, bindings, and service endpoints. Make sure that the behavior and service names include the project namespace and, in the case of the service endpoints, the data service class name.
There are numerous posts on the web about getting WCF to work with various bindings and security configurations. After much trial and error, I have found the following configuration to work flawlessly with IIS and AJAX XMLHttpRequest operatins using both GET and POST operations via JQuery.
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="BWAppDataModel.DataAspNetAjaxBehavior">
<enableWebScript />
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="httpBinding" crossDomainScriptAccessEnabled="true">
<security mode="None" />
</binding>
</webHttpBinding>
</bindings>
<services>
<service name="BWAppDataModel.AppDataService">
<endpoint address="" behaviorConfiguration="BWAppDataModel.DataAspNetAjaxBehavior" binding="webHttpBinding" bindingConfiguration="httpBinding" contract="BWAppDataModel.AppDataService" />
</service>
</services>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
NOTE: The sample shows an HTTP endpoint but the SharePoint 2013 framework requires that all apps run over SSL. In addition, the webHttpBinding is configured to support JSONP for cross-domain calls but the sample application adheres to the default same origin policy. |
3 |
Create a Data Contract
The entity model created for the sample project includes one data object, the UserInfo table, and two functions – spGetUser and spUpsertUser. The GetUser request does not require a serialized object to be passed to an operation contract – a simple GET operation with a query string parameter will suffice. The UpsertUser function, however, requires a complex object that contains all of the UserInfo properties (fields like UserName, EmailAddress, Phone, etc.). The parameter for the POST operation must be mapped to an object with string data members for each field that precisely match the field names and field order of a JSON object created on the client.
To create a data contract for the upsert operation, first create a new class to store the data contracts. Right-click on the web application project and select Add > Class, supplying an appropriate class name such as "DataContracts":
Add a using statement for System.Runtime.Serialization and create a public data contract class within the base class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Runtime.Serialization;
namespace BWAppDataModel
{
public class DataContracts
{
[DataContract]
public class UserInfoObject
{
}
}
}
Add variables and DataMember properties to the UserInfoObject class. Each DataMember property must be a string that matches the incoming JSON object:
[DataContract]
public class UserInfoObject
{
private string _userId;
private string _loginName;
private string _firstName;
private string _lastName;
private string _email;
private string _phone1;
private string _phone2;
private string _address1;
private string _address2;
private string _city;
private string _state;
private string _zip;
private string _country;
private string _active;
private string _isEngineer;
private string _imageUrl;
[DataMember]
public string UserId
{
get { return _userId; }
set { _userId = value; }
}
[DataMember]
public string LoginName
{
get { return _loginName; }
set { _loginName = value; }
}
[DataMember]
public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}
[DataMember]
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}
[DataMember]
public string Email
{
get { return _email; }
set { _email = value; }
}
[DataMember]
public string Phone1
{
get { return _phone1; }
set { _phone1 = value; }
}
[DataMember]
public string Phone2
{
get { return _phone2; }
set { _phone2 = value; }
}
[DataMember]
public string Address1
{
get { return _address1; }
set { _address1 = value; }
}
[DataMember]
public string Address2
{
get { return _address2; }
set { _address2 = value; }
}
[DataMember]
public string City
{
get { return _city; }
set { _city = value; }
}
[DataMember]
public string State
{
get { return _state; }
set { _state = value; }
}
[DataMember]
public string Zip
{
get { return _zip; }
set { _zip = value; }
}
[DataMember]
public string Country
{
get { return _country; }
set { _country = value; }
}
[DataMember]
public string Active
{
get { return _active; }
set { _active = value; }
}
[DataMember]
public string IsEngineer
{
get { return _isEngineer; }
set { _isEngineer = value; }
}
[DataMember]
public string ImageUrl
{
get { return _imageUrl; }
set { _imageUrl = value; }
}
} |
4 |
Create Operation Contracts
In the data service class, create a new operation contract that accepts and returns JSON. The type of operation, either GET or POST, is specified by the first attribute (WebGet and WebInvoke, respectively) and the WebMessageBodyStyle, RequestFormat and ResponseFormat define the supported message types.
namespace BWAppDataModel
{
[ServiceContract(Namespace = "BWAppDataModel")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class AppDataService
{
[OperationContract]
[WebGet(BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
public string GetUser(string UserId)
{
}
}
}
Setup the Entity Model connection by instantiating a new instance of the container within a using statement.
[OperationContract]
[WebGet(BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
public string GetUser(string UserId)
{
using (EntityModelContainer entities = new EntityModelContainer())
{
}
}
|
5 |
Get Data from Entity Objects
Getting data from the entity objects is a simple matter of calling the mapped function and binding the result to a variable. First, create a return response variable, then make a call to the desired function (imported earlier from the matching stored procedure in the database), passing in the strongly-typed parameters required by the procedure and mapping the response to the correct entity object.
[OperationContract]
[WebGet(BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
public string GetUser(string UserId)
{
using (EntityModelContainer entities = new EntityModelContainer())
{
string response = string.Empty;
try
{
UserInfo user = entities.spGetUser(Convert.ToInt32(UserId)).FirstOrDefault();
}
catch (System.Exception ex)
{
}
return response;
}
}
It is often necessary to manipulate the data before returning it to the client. For example, DateTime objects are represented in JSON as "\/Date(1198908717056)\/". This isn't very conducive for control binding or sorting values in a grid. Instead, it's often preferable to convert the date into standard "MM/dd/YYYY" format. You may also wish to concatenate fields (such as FirstName and LastName) or add fields that don't exist in the original dataset. To manipulate the response before it's serialized, use LINQ to Objects on the collection like so:
List<UserInfo> _users = entities.spGetUser(Convert.ToInt32(UserId)).ToList();
var users = from u in _users
select new
{
UserId = u.UserID,
LoginName = u.LoginName,
FullName = u.FirstName + " " + u.LastName
};
Handling POST operations is done in a similar fashion but the contract is defined differently. Instead of WebGet the WebInvoke attribute is used and the parameter is mapped to a DataContract object instead of individually declared query string variables.
[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
public string UpsertUser(DataContracts.UserInfoObject User)
{
}
It is important to note that the name of the fields in the DataContract object and the order they appear in must match the JSON object defined on the client or the operation will fail. The process for inserting, updating and deleting data is the same as that used to retrieve data – instantiate the container object, call the function, and get back a typed result (or, if the procedure returns no data, no result at all).
[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
public string UpsertUser(DataContracts.UserInfoObject User)
{
using (EntityModelContainer entities = new EntityModelContainer())
{
string response = string.Empty;
try
{
entities.spUpsertUser(Convert.ToInt32(User.UserId),
User.LoginName,
User.FirstName,
User.LastName,
User.Email,
User.Phone1,
User.Phone2,
User.Address1,
User.Address2,
User.City,
User.State,
User.Zip,
User.Country,
Convert.ToBoolean(User.Active),
Convert.ToBoolean(User.IsEngineer),
User.ImageUrl);
}
catch (System.Exception ex)
{
}
return response;
}
}
|
6 |
Return JSON Data
The call to the entity model function will execute the stored procedure and return a typed object; however, the object (or collection of objects), must first be converted to JSON format before it can be sent back the requestor. Fortunately, there is a web script serialization class available to transform the data. First, put the object or object collection into an anonymous type as an array then call the serializer to format the data and assign it to the return variable.
[OperationContract]
[WebGet(BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
public string GetUser(string UserId)
{
using (EntityModelContainer entities = new EntityModelContainer())
{
string response = string.Empty;
try
{
UserInfo user = entities.spGetUser(Convert.ToInt32(UserId)).FirstOrDefault();
var data = new { user };
JavaScriptSerializer s = new JavaScriptSerializer();
response = s.Serialize(data);
}
catch (System.Exception ex)
{
var data = new { Error = ex.Message };
JavaScriptSerializer s = new JavaScriptSerializer();
response = s.Serialize(data);
}
return response;
}
}
The result of this transformation will be a string similar to the following:
{"d":"{\"user\":[{\"UserId\":\"4fca5677-8e0d-4a72-93c0-eec928379b21\",\"LoginName\":\"jsmith\",
\"FirstName\":\"John\",\"LastName\":\"Smith\",\"Email\":\"jsmith@gmail.com\",
\"Phone1\":\"888-387-1197\",\"Address\":\"611 S. Main Street\",\"State\":\"TX\",\"Country\":\"US\",\"EntityState\":2,\"EntityKey\":{\"EntitySetName\":
\"user\",\"EntityContainerName\":
\"EntityModelContainer\",\"EntityKeyValues\":[{\"Key\":\"UserId\",\"Value\":\"4fca5677-8e0d-4a72-93c0-eec928379b21\"}],\"IsTemporary\":false}}]}"}
To handle any exceptions, wrap the operations in a try…catch block and return the exception message in the same serialized format. This can then be processed by the client in a uniform manner to identify any error conditions.
In cases where the function does not return any data a success or failure message should still be passed to the client. This can be done by declaring a response object message (such as "Success") inline for serialization. The client can then check for a "Success" or "Error" object and handle each accordingly. |
| |
[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
public string UpsertUser(DataContracts.UserInfoObject User)
{
using (EntityModelContainer entities = new EntityModelContainer())
{
string response = string.Empty;
try
{
entities.spUpsertUser(Convert.ToInt32(User.UserId),
User.LoginName,
User.FirstName,
User.LastName,
User.Email,
User.Phone1,
User.Phone2,
User.Address1,
User.Address2,
User.City,
User.State,
User.Zip,
User.Country,
Convert.ToBoolean(User.Active),
Convert.ToBoolean(User.IsEngineer),
User.ImageUrl);
var data = new { Success = "Success" };
JavaScriptSerializer s = new JavaScriptSerializer();
response = s.Serialize(data);
}
catch (System.Exception ex)
{
var data = new { Error = ex.Message };
JavaScriptSerializer s = new JavaScriptSerializer();
response = s.Serialize(data);
}
return response;
}
} |
Regardless of how many functions and operations there are in the service, the syntax is identical for each one – either a WebGet or WebInvoke operation is declared, the container is wrapped in a using statement, a function or other block of code is executed, and the response returned in JSON format. Each new operation is cut and paste from the last with minor alterations as required. This conforms to the original objective of the pattern for new operations to be as simple as copy => paste => tweak => deploy. The process is easy to remember and repeat:
- Create stored procedure
- Import procedure
- Create data contract (if necessary)
- Copy and paste either WebGet or WebInvoke operation contract
- Modify operation name and parameters (query string variables or data contract object)
- Replace function call and return type
- Manipulate dataset (if necessary)
- Specify response object
- Response is serialized and returned
For those who prefer not to use stored procedures, need to access a remote data store (possibly even calling another web service), or just need to execute a batch of code that doesn't pertain to anything in the entity model, the process remains the same, only the function call and return type are replaced with the requisite code. It's repetitive almost to the point of being monotonous but that's the beauty of the pattern – developers don't need to think about what each operation is doing. They simply make a few edits and carry on to the next operation. As uniform and consistent as this portion of the pattern is, it should even be possible for someone skilled at creating Visual Studio plugins to automatically generate the entire set of data and operation contracts from the entity model and function imports with a simple wizard. That kind of automation would really accelerate app development. For now, the process of cut and paste operation contracts defined in the pattern is at least efficient enough to remove the barriers of working with moderately complex data sets and increase developer productivity.
In Part One of this series a data access layer was creating using the ADO.NET Entity Framework. In Part Three the middle and backend tiers will be exposed to the front-end JavaScript using AJAX calls and JQuery, allowing for a responsive UI with no code-behind or server dependencies. The full Visual Studio solution will be available for download at the conclusion of Part Three.
Additional References
What is Windows Communication Foundation
Getting Started Tutorial for WCF
AJAX Integration and JSON Support
Entity Framework 4.0 and WCF Services 4.0
|
| At SharePoint Conference 2013 I demonstrated an Azure-hosted help desk application for Office365 and SharePoint 2013 using the new app model framework. The core data for the application was stored in a SQL Azure database with some SharePoint data integration via REST. The entire UI was coded in JavaScript and HTML. This presented some interesting challenges during the development process and led to the creation of a repeatable pattern that I have used in several subsequent projects. I've received a number of requests to share the code but I felt that it deserved a more complete explanation and walkthrough than a sample Visual Studio solution can provide on its own. This series of posts lays out the thought process behind the pattern along with sample code and implementation details so developers can determine if it is suitable for their purposes and how best to modify it in each unique situation.
Overview
Any development pattern is only as good as the situation(s) it was originally designed for. My scenario for the initial application was very specific – a SharePoint app that could run both in the cloud and on-premise, with the provider-hosted web and database both in Azure, featuring a modern UI that only used server-side code where absolutely necessary (such as authorization and the Client Object Model). It should be noted, however, that the same pattern has been applied to non-SharePoint projects outside of Azure with very little modification. Essentially, the only requirement is an HTML + JavaScript web site built with Visual Studio that needs to exchange data with a SQL server database; for purposes of the pattern, SharePoint and Azure are simply extensions.
Conceptually, the pattern seeks to achieve three primary objectives: 1) Provide a consistent methodology for CRUD operations from client-side script to/from a remote data repository, 2) Define a middle-tier service layer with uniform object and method structures, and 3) Minimize the amount of manual coding required to create and manage core data access classes. In practice, the pattern is primarily intended to ease the process of client and server side code integration; secondarily, it aims to make each operation as simple as copy => paste => tweak => deploy. From this perspective, it is not an architectural pattern in the traditional sense of MVC or SOA but rather a simplistic design pattern – an approach to dealing with specific implementation challenges in a consistent and repeatable manner.
The scenarios to which the pattern applies are somewhat narrowly focused in that they deal almost entirely with components within the overall Microsoft .NET development stack – AJAX, WCF, C#, SQL, and, of course, Visual Studio – but within that stack these are widely used and nearly universal in all modern web development projects designed to run on Windows servers. Bringing SharePoint back into the equation, this collection of components, although not required for the 2013 app model, will almost certainly comprise the vast majority of all apps both in the cloud and on-premise, making the pattern especially useful in that context. For applications such as these to which the pattern is applicable, the basic execution process resembles the following:
With a corresponding return path:
To better understand the process, consider a use case in which a user must click a button to display a set of tabular data in a grid. The following activities would then take place behind the scenes in order to achieve the desired result:
- A JavaScript function attached to the HTML button element fires in response to the button click event.
- The function constructs an object in JSON notation.
- The JSON object is posted via an AJAX request to a WCF endpoint.
- The specified OperationContract method maps the JSON object to a pre-defined DataContract object.
- An Entity object is instantiated.
- A function associated with the Entity is invoked using parameters represented by the DataContract's DataMember elements.
- A stored procedure mapped to the Entity object function is called and passed the appropriate parameters.
- The procedure executes and returns a result set (if data is the specified output, otherwise no data or a status message is returned).
- The result set is mapped to a collection of complex entity types.
- The entity collection is transformed into a JSON object.
- The OperationContract returns the object as a string.
- An asychronous success or failure handler in JavaScript receives the response.
- The data is extracted from the JSON string.
- JQuery (or direct JavaScript) is used to manipulate objects in the DOM or display informational dialogs.
- The UI is updated to display the final result.
The use case and execution process can be distilled down into an architecture that consists of three tiers – a backend data tier based on the ADO.NET Entity Framework, a middle tier using WCF web services, and a front-end tier using JavaScript, JQuery and AJAX. Naturally, many developers will disagree with various aspects of the pattern and the chosen implementation methods. This is to be expected – everyone designs applications in their own unique fashion. The pattern is simply a guideline designed to make repetitive coding tasks easier and less cumbersome. Any part can be switched out for an alternate method that satisfies the same requirements. For example, some .NET developers like the Entity Framework and others don't, preferring to write their own data access classes. A solid argument can be made that EF is too rigid and the generated code overly verbose. These are fair criticisms; however, many developers find it to be much more efficient than manual code generation and it was selected for the pattern solely on this basis. If you prefer to roll your own data methods or use some other framework, skip Part One and simply wire up the WCF methods in Part Two to something else more to your liking.
On a similar note, WCF may not be the most desirable middle-tier component. In this respect, the pattern is a bit more rigid – WCF is the glue that ties the front end to the back end and, until something else comes along, is the preferred SOA solution for .NET apps. That being said, developers who prefer the legacy ASMX approach can continue to use it as part of the pattern but the provided code samples will all be based upon WCF.
The front-end is the most flexible part of the pattern. Any application that can call a remote web service and parse JSON data can serve as the client. This includes platform-specific mobile apps (IOS, Android, Windows Phone), rich applications (Silverlight, WPF, Windows 8) and any modern web browser. The pattern was designed specifically to eliminate any client-side platform dependencies (other than JSON but even that could be swapped for XML with some minor changes to the middle tier).
Implementation
In the context of building SharePoint apps, the most logical implementation of the pattern is an IIS web server running in the cloud or on-premise that hosts the web application and WCF service. A SharePoint 2013 or Office365 site provides access to the app, secured via OAuth or S2S, which exposes a responsive UI with little or no code behind (or at least the minimal amount of code required for authorization and context management). All data access is facilitated by the WCF service via JavaScript AJAX calls using JSON. Access to SharePoint is handled via REST and the client object model (which is not part of the pattern but could easily be integrated). Regardless of the actual functionality provided by the app, this should be a fairly consistent theme, with minor variations (such as the front-end hosting platform), for most apps that end up either in the app store or corporate catalog.
The first implementation concern is the data layer, which involves the creation of Entity Framework objects and functions. First, an entity class is created, then a connection to a database established, followed by the import of data objects (such as tables and stored procedures). Behind the scenes, the wizard will generate code for each set of objects which can then be accessed from the service layer. Although the sample steps shown in this series involve an ASP.NET web site running in an Azure web role, the data model can exist independently or in combination with the service layer. The latter implementation scenario is mostly applicable to rich clients and mobile devices. The second phase, covered in Part Two, will focus on the WCF service, data contracts, operation contracts and configuration parameters. Accessing the data asynchronously and wiring up the final UI elements will be covered in Part Three.
1 |
Create a Web Application
Begin by creating a web application to host the app and web service (these can be de-coupled if necessary but be aware of cross-domain issues when doing so).
Open Visual Studio 2012, create a new project and select Cloud > Windows Azure Cloud Service.
Visual Studio will prompt you to select a role type. Any web role will work – the ASP.NET role is used here but an MVC role will work just as well if you prefer that development model. Select the desired role, click the right > arrow, and edit the solution name:
Click OK and VS will generate a solution with projects for the web role and Azure deployment.
|
2 |
Add an Entity Model
Add an ADO.NET Entity Data Model to the web role project. This model will provide for the automatic generation of data objects from SQL and saves a tremendous amount of time and effort compared to manual creation of data access classes.
If you wish to import an entire data structure and let the wizard wire up all the necessary objects, you can select "Generate from database" at this stage. Otherwise, start with an empty model and manually add the objects later.
You may be prompted with a few security warnings at this point. This occurs when VS tries to open the model design surface for the first time. You can safely ignore these warnings.
|
3 |
Connect to SQL and Import Objects
Right-click on the Entity Model design surface and select Update Model from Database. At this point you will be prompted to create a new data connection.
Click New Connection then select Microsoft SQL Server and click Continue.
In the server name, either select the local SQL server instance or, if you are working with a SQL Azure database, input the fully-qualified URL of the database (you can find your connection information in the Azure portal by going to the SQL Databases tab, selecting the database from the list, and clicking Show connection strings on the right). Enter the login credentials and click Test Connection (remember that Azure uses SQL Server authentication and the user name will be in the format [UserName]@[ DatabaseName] – with DatabaseName being the unqualified string name of the SQL database instance as shown in the Azure portal).
If you haven't already added your machine to the Azure firewall list, you will likely receive the following error:
This can be corrected by selecting the database and clicking Manage on the Actions tab at the bottom of the Azure dashboard. It will prompt you to add your current IP address to the firewall rules.
Once the connection is successful, clicking OK will populate the data connection fields in the wizard. You can select to either embed the connection password in the web.config settings or set it programmatically then click Next.
If all the settings are correct and you don't run into the Pre-Login handshake SSL error, the wizard will then display a list of objects from the database:
Expand the Tables node and select a table to import. Click Finish and the table object will be imported and added to the model design surface:
Add Function Imports
Mapping entity objects to stored procedures is accomplished via Function Imports in the Entity Framework. To wire up a procedure, select the Model Browser tab at the bottom of the Solution Explorer window. This will display the model hierarchy.
Begin the procedure mapping by first importing a stored procedure from the database. Right-click in the model designer and select Update Model from Database. Expand the Stored Procedures node, locate the desired procedure, and click Finish.
The procedure will be imported and added to the list of function imports. By default, this will create a function with the same name as the procedure and a new complex type based on the fields returned by the procedure. If a direct mapping to an entity object is desired, rather than a separate complex type, edit the properties of the function, select Entities, and then choose the appropriate object (NOTE: the object must have the same properties and data types as those returned by the procedure for the mapping to succeed).
Repeat this process for each table and procedure in the database; alternatively, you can do a mass import of tables and procedures in the Update step then clean up any individual objects that require different return types.
|
The above process creates the basic data structure for the app. Whenever new tables or procedures are added during the development process, the Update Model from Database step is repeated to create the necessary objects and functions. If any changes are made in the underlying data structure (i.e. columns are added to a table or the data type of a field returned by a procedure changes), use the Refresh tab in the update wizard to modify the generated code. Be aware that any manual changes made to the underlying code files will be overwritten during a refresh operation. To make minor changes to a function, edit the function in the Model Browser and update the generated complex type using the Update button or simply create a new one (mappings to Entity objects are necessarily static so use a complex type if there is no direct column mapping or if you desire the ability to change the result set without changing core entities).
In Part Two of this series a WCF service will be created that utilizes the entity container and objects to serve as a middle-tier data broker between the UI and the database. In Part Three the middle and backend tiers will be exposed to the front-end JavaScript using AJAX calls and JQuery, allowing for a responsive UI with no code-behind or server dependencies. The full Visual Studio solution will be available for download at the conclusion of Part Three.
Additional References
Getting Started with Windows Azure SQL Databases
Windows Azure and SQL Database Tutorials
How to Create and Deploy a Windows Azure Cloud Service
ADO.NET Entity Framework
Get Started with the Entity Framework
|
| As another new year begins it's a great time to look ahead and start planning for all the great learning opportunities in the SharePoint community. In addition to local events like user groups, code camps, weekend gatherings and the like, there are also a number of regional and international conferences on the schedule. Everyone has their favorite events but the one I look forward to the most is our annual shindig in London. In keeping with past tradition for years in which a new version of the product is released, the International SharePoint Conference will be re-branded as the SharePoint Evolution Conference for 2013. For three days in April, from the 15th through the 17th, we'll be taking over the Queen Elizabeth II Conference Centre with dozens of top-notch speakers and tons of engaging content.
This year the schedule contains no less than seven individual tracks, including two dedicated just to information workers, with over a hundred unique sessions and endless networking opportunities. While many conferences focus only on technical content, this event includes dedicated tracks for business-minded individuals as well. No matter what your place is in the SharePoint ecosystem, you'll find hours of quality information targeted to your particular niche. And what better place to bring all the best and brightest together than the west side of London, just a stone's throw from Westminster Abbey and the Houses of Parliament?
I often get asked by those who have heard about it but never attended what makes this particular conference so special. I think the most important reason is that it's really a labor of love by Steve Smith and the Combined Knowledge crew with tremendous support from the global SharePoint Community. Unlike commercial events that are driven by the bottom line, and usually run by folks who don't live and breathe SharePoint, the International/Evolution conference is put on by SharePoint people for SharePoint people. It's a community event on a much larger scale with all the professionalism and polish of a big conference but none of the fluff. It feels like a community event and not a stuffy corporate affair – the venue is moderately sized, the number of vendors is manageable, the presenters are approachable and the knowledge sharing is organic not marketing-driven.
If you live outside the US and can only attend one SharePoint event each year, I honestly believe that you should pull out all the stops to make sure you're in in London come April 15th. The wide range of speakers, variety of topics, quality of content, networking opportunities and location simply cannot be beat. And the attendee parties are the stuff of legend. So get out your calendars, mark off the dates, and be sure to join us in London for SharePoint Evolution Conference 2013 – you'll be glad you did!
|
| When Lenovo started shipping hybrid graphics in their W500-series workstations, it was a great improvement for road warriors who need both full-power graphics when connected to a power source and long battery life on the go. This is achieved by the presence of two video cards – an integrated Intel chip side-by-side with an nVidia Quadro GPU. The nVidia drivers ship with software that automatically switch between graphics modes based on the current power profile.
This configuration works great until the machine is connected to an external monitor or projector via the VGA port. For some reason, the nVidia software is unable to automatically balance the output between both video cards when the display is duplicated. Many users have resorted to disabling one or the other video cards in the BIOS, which works fine but requires that the change be made manually each time to computer is rebooted (assuming that the user wants the low power mode at some point – if the machine is always plugged in then it's really not an issue).
There is, however, a way to get display duplication working with the auto-switching Optimus mode. All the configuration options are there in the nVidia control panel but the configuration isn't very intuitive. Here's how to make it work (this holds true for both Windows 7 and Windows 8, although the sub-menu text in the control panel is a bit different between driver versions):
1. Open the nVidia Control Panel application. Go to the multiple display management section.
2. Connect the external monitor or display. The display selector will update and show the new device. By default, the external device shows up as a second display in both sections but is only enabled under the Intel adapter.
|

|
3. Uncheck the external device under the Intel adapter then check it under the Quadro adapter. The display icons below the selection area will then change to show two screens side-by-side.
|

|
4. Right-click on the display icons and choose "Clone with" and choose one of the displays. Which display you choose varies from display to display – try one and if that doesn't work then try the other (on the upside, you have a 50/50 chance of being right the first time). If you select the wrong one, it is likely that the display will switch to the external device only, making it a bit awkward to change back. Let the timer run down on the new configuration or disconnect the cable and try again.
|

|
5. Once you've selected a clone target the multiple display screen will change and the two display icons will merge into one with a circle for each display. The output should now be duplicated to both screens.
|

|
When the process is complete, it may be necessary to use Windows + P and choose the Duplicate option (but it will often switch over on its own). In most cases, Optimus will remember the configuration and connections to new devices will work automatically. This isn't always true, especially if the display is older and doesn't broadcast the right configuration data, so you may need to repeat the process when connecting to a new external display. This will allow you to leave the machine in Optimus mode, getting the benefit of long battery life when disconnected from a power source and the full Quadro GPU power when connected without having to choose one or the other when presenting.
|
| In the new SharePoint 2013 App model, there are essentially two ways to host apps – within SharePoint itself or from an external web site (also known as "provider hosted" or "autohosted"). One of the disadvantages of external apps is that they don't look or feel like SharePoint. All the familiar navigation menus and shortcuts are missing, resulting in a stark contrast between the default SharePoint visual experience and whichever app is currently being used unless the app developer went the extra mile (or ten) to style their app.
While this isn't really a bad thing – the app is fully functional and can communicate with SharePoint – it doesn't quite lend itself to a cohesive user experience. To bridge this gap, Microsoft allows developers to import a very basic version of the SharePoint 2013 chrome into their apps without having to manually create matching HTML controls. The functionality for this can be found in the SP.UI.Controls.js file located in the new /_layouts/15 directory. To use the chrome control, first add a reference to SP.UI.Controls.js (make sure you've already loaded the requisite JQuery files and other dependencies), then add an empty <div> to your page markup at or near the top of the page:
Next, invoke the chrome methods in your javascript file. The first step is to get the SPHostUrl value from the query string (which is appended by SharePoint when posting to your app). Then create a collection to hold the chrome control option values. Finally, instantiate a new Navigation object using the container <div> added to the page markup in the previous step and make the object visible.
Note that the hostWebUrl variable is set with a call to decodeURIComponent that passes a function – getQueryStringParameter(string) - as a parameter. This is simply a helper function (with no relation to the navigation code) that parses out the host URL value as there are several variants for the host URL that can be set in the app.manifest file (and any number of custom parameters). The code for this function looks for the provided key and returns the appropriate value, like so:
The values in the 'options' object aren't required but are helpful in decorating the chrome. In this example, the rendered HTML will include the host site name along with the app name and links back to the SharePoint site using the hostWebUrl value. Although these are hardcoded in the example, they could also be dynamically set in the code behind of the page after client context is established via TokenHelper, stored in hidden fields, and read into the control when the options object is created. When the app is executed, functions in the Chrome script call back to the host web, extract the themed markup, and render it within the designated control, as seen in the following snippet:
As you can see, the output is rather rudimentary. If you were expecting a full reproduction of the SharePoint 2013 UI you will be sorely disappointed. There's just enough markup to establish a visual connection to the site the app was launched from – no more, no less. But, at least it's a start; perhaps in the future we'll gain deeper access into the host site design elements. For more information on the Chrome control, refer to the following MSDN article: http://msdn.microsoft.com/en-us/library/fp179916(v=office.15).aspx.
|
| Well, it's that time of year again, and Microsoft has seen fit to keep me in the MVP program for another go around. This will be my sixth year as an MVP. When I first came into the program there were only a few dozen SharePoint MVP's - now there are hundreds all over the world. The yearly MVP summit used to be an opportunity to catch up with old friends you didn't see very often but with the growth of the group (which is still quite small compared with groups like SQL or ASP.NET) it's now become a way to meet new people you might not have even heard of before.
For those of us who have been around a while, the quarterly renewal cycles can often be bittersweet - sometimes we have to say goodbye to friends who have moved on or focused their community efforts in other directions. Despite what some people on the outside who have no knowledge of the inner workings of the program may think, it still takes a lot of work to attain and retain MVP status. You don't have to be the brightest technical guru or constantly on the speaking circut to become an MVP (although bouth of those certainly help) but you do have to put in the hours contributing to the community in whatever fashion suits you. Not everyone has the capability or flexibility to continue contributing at a high level - life and circumstances change and sometimes there's just not enough hours in the day. It's sad to see good people leave the program but always exciting to meet the new kids on the block.
So congratulations to all the new and returning SharePoint MVP's. With a new product release on the horizon it's going to be a busy year ahead. Be sure to join us all in Las Vegas on November 12th - 15th for SharePoint Conference 2012 for lots of learning and plenty of partying! |
| Most commercial blogging platforms use a fixed URL structure, known as a "permalink", to identify posts – something like "http://www.myblog.com/2012/09/13/This-Is-My-Post.html". This structure makes it easy to reference a particular post, supports trackbacks/pingbacks, and makes it simple for search crawlers to locate content. Unfortunately, the SharePoint 2010 Blog template has no concept of how an actual blog platform should work – permalinks are an entirely foreign concept. Because SharePoint stores blog posts as individual list items, the URL structure is comprised of a fixed root path with a query string parameter for the item ID, such as "http://www.binarywave.com/blogs/eshupps/Lists/Posts/Post.aspx?ID=270". Awful stuff.
Short of writing an HTTP Handler to construct friendly URL's, SharePointers are stuck with this less-than-optimal format. That being said, it would be helpful if the various views for the Posts list in a blog actually used this link structure to refer to each item but, alas, they don't; instead, they use and even worse path identifier that references an application page in the _layouts directory with parameters for the list and item ID (i.e. "http://www.myblog.com/_layouts/listform.aspx?PageType=4&ListId={89CBE813-99F7-4257-A23A-5FEFC377336B}&ID=269"). This is the equivalent of not only forgetting to put bullets in your gun but leaving the gun behind at the saloon on your way to the OK Corral – in other words, it makes an already bad situation even worse.
This makes summary listings of blog entries, such as the Archives view or Categories, next to impossible for search engines to follow and other bloggers to reference. If your blog happens to be a subsite under a publishing web it's very likely that the Lockdown feature has been enabled which prevents readers from even accessing content in the _layouts directory. And if you are doing any type of SEO on your site at all then you likely have a robots.txt file with DISALLOW entries for system directories like _layouts, _vti_bin, _catalogs, etc. It's enough to make you want to pull your hair out (if you have any left at this point).
Strangely enough, if you examine the views on the Posts list you'll see a field entitled "Permalink". Now that sounds like a good solution to this problem, doesn't it? If only it were so. The permalink field is a computed column that renders an icon with the proper link to the post (as proper as it gets, anyway – it's still referencing '/Lists/Posts/Post.aspx?ID=[ItemID]') instead of a hyperlink wrapped around the Title text. You'll see this at the bottom of each post next to the email link and number of comments. That's just throwing salt in the wound – first, take away real permalink functionality, then give us a field that almost-but-not-quite renders a semi-permalink. Oh, happy day.
So what can we do to fix this problem? Well, to begin with, we can add a column for the permalink value we want to create, the value of which should look something like "< a href='http://www.myblog.com/Lists/Posts/Post.aspx?ID=1' >My Post Title< /a >" (spaces added to fool the rich text editor); however, if we just create a computed column (which would be the simplest solution) then SharePoint will spit out the text of our markup and not an actual hyperlink (so much for simple solutions). So we need to use a Hyperlink column, which requires the format "Link, Text", meaning our input should look like "http://www.myblog.com/Lists/Posts/Post.aspx?ID=1, My Post Title". Ok, great, but how do we get that into the new column without manually having to input it each time we create a new post? And, even more challenging, how do we go back and update all of our old posts to use the new value?
There are a couple of ways to approach the first part of the problem, one with code and one without. Using code, we can write an event receiver to fire on ItemUpdated, parse out the ID and Title, then set the link column value and update the list item. Using a no code approach, we can use a workflow to do essentially the same thing – run on created or updated, set the link field value to a dynamic string formatted as "Link, Title", and use lookups to get the ID and Title values. Simple enough and should work just fine on new items. But what about fixing up the links on all of the existing items?
This can be done either with code (probably a simple console app) or Powershell. As writing an executable is probably overkill for this particular situation, Powershell would be a quicker way to get the desired result. Either way, the process is identical – get the list, loop through the list item collection, extract the Title and ID values from each list item, construct a string and store it in the link field. Here's a script to do just that:
$site = new-object Microsoft.SharePoint.SPSite("http://www.myblog.com") $web = $site.openweb() $list = $web.lists["Posts"] $items = $list.getItems() foreach ($i in $items) {$i["PostLink"] = "http://www.binarywave.com/blogs/eshupps/Lists/Posts/Post.aspx?ID=" + $i.ID + ", " + $i.Title;$i["_ModerationStatus"] = 0;$i.update();}
There are a couple of things to note about this script. First, "PostLink" is the field that holds the link value (the name is arbitrary). It is an out-of-the-box Hyperlink field added to the Posts list. Second, don't forget to call Update() on each item or the changes won't persist to the database. Finally, and this one will give you heart palpitations if you forget to include it, be sure to Approve each list item by setting the _ModerationStatus field to zero (unless you've turned off approvals for the Posts list – it's enabled by default); otherwise, when you go back to your site to view the wonderful results of your handiwork you'll be presented with a blank web part on the default page of your blog. Manually approving hundreds of list items is nobody's idea of a good time.
The last step is simply to alter the various views of the Posts list (such as "Archive") to use the new link value field instead of the "Title [Linked to Item with Edit Menu]" field. Now you've got all the bases covered – the workflow will handle creation of the permalink when new posts are created, the Powershell script fixed up all the existing items, and the views contain the proper links. Job done.
Now, if we could only get a REAL permalink field in the SharePoint blog template the world would be a much happier place – birds would sing, the sun would shine, money would grow on trees, Dr. Pepper would flow in streams, beer would be free…yeah, whatever. Don't hold your breath. |
Manage Subscriptions /_layouts/images/ReportServer/Manage_Subscription.gif /blogs/eshupps/_layouts/ReportServer/ManageSubscriptions.aspx?list={ListId}&ID={ItemId} 0x80 0x0 FileType rdl 350 Manage Data Sources /blogs/eshupps/_layouts/ReportServer/DataSourceList.aspx?list={ListId}&ID={ItemId} 0x0 0x20 FileType rdl 351 Manage Shared Datasets /blogs/eshupps/_layouts/ReportServer/DatasetList.aspx?list={ListId}&ID={ItemId} 0x0 0x20 FileType rdl 352 Manage Parameters /blogs/eshupps/_layouts/ReportServer/ParameterList.aspx?list={ListId}&ID={ItemId} 0x0 0x4 FileType rdl 353 Manage Processing Options /blogs/eshupps/_layouts/ReportServer/ReportExecution.aspx?list={ListId}&ID={ItemId} 0x0 0x4 FileType rdl 354 Manage Cache Refresh Plans /blogs/eshupps/_layouts/ReportServer/CacheRefreshPlanList.aspx?list={ListId}&ID={ItemId} 0x0 0x4 FileType rdl 355 View Report History /blogs/eshupps/_layouts/ReportServer/ReportHistory.aspx?list={ListId}&ID={ItemId} 0x0 0x40 FileType rdl 356 View Dependent Items /blogs/eshupps/_layouts/ReportServer/DependentItems.aspx?list={ListId}&ID={ItemId} 0x0 0x4 FileType rsds 350 Edit Data Source Definition /blogs/eshupps/_layouts/ReportServer/SharedDataSource.aspx?list={ListId}&ID={ItemId} 0x0 0x4 FileType rsds 351 View Dependent Items /blogs/eshupps/_layouts/ReportServer/DependentItems.aspx?list={ListId}&ID={ItemId} 0x0 0x4 FileType smdl 350 Manage Clickthrough Reports /blogs/eshupps/_layouts/ReportServer/ModelClickThrough.aspx?list={ListId}&ID={ItemId} 0x0 0x4 FileType smdl 352 Manage Model Item Security /blogs/eshupps/_layouts/ReportServer/ModelItemSecurity.aspx?list={ListId}&ID={ItemId} 0x0 0x2000000 FileType smdl 353 Regenerate Model /blogs/eshupps/_layouts/ReportServer/GenerateModel.aspx?list={ListId}&ID={ItemId} 0x0 0x4 FileType smdl 354 Manage Data Sources /blogs/eshupps/_layouts/ReportServer/DataSourceList.aspx?list={ListId}&ID={ItemId} 0x0 0x20 FileType smdl 351 Load in Report Builder /blogs/eshupps/_layouts/ReportServer/RSAction.aspx?RSAction=ReportBuilderModelContext&list={ListId}&ID={ItemId} 0x0 0x2 FileType smdl 250 Edit in Report Builder /_layouts/images/ReportServer/EditReport.gif /blogs/eshupps/_layouts/ReportServer/RSAction.aspx?RSAction=ReportBuilderReportContext&list={ListId}&ID={ItemId} 0x0 0x4 FileType rdl 250 Edit in Report Builder /blogs/eshupps/_layouts/ReportServer/RSAction.aspx?RSAction=ReportBuilderDatasetContext&list={ListId}&ID={ItemId} 0x0 0x4 FileType rsd 250 Manage Caching Options /blogs/eshupps/_layouts/ReportServer/DatasetCachingOptions.aspx?list={ListId}&ID={ItemId} 0x0 0x4 FileType rsd 350 Manage Cache Refresh Plans /blogs/eshupps/_layouts/ReportServer/CacheRefreshPlanList.aspx?list={ListId}&ID={ItemId}&IsDataset=true 0x0 0x4 FileType rsd 351 Manage Data Sources /blogs/eshupps/_layouts/ReportServer/DataSourceList.aspx?list={ListId}&ID={ItemId} 0x0 0x20 FileType rsd 352 View Dependent Items /blogs/eshupps/_layouts/ReportServer/DependentItems.aspx?list={ListId}&ID={ItemId} 0x0 0x4 FileType rsd 353 Compliance Details javascript:commonShowModalDialog('{SiteUrl}/_layouts/itemexpiration.aspx?ID={ItemId}&List={ListId}', 'center:1;dialogHeight:500px;dialogWidth:500px;resizable:yes;status:no;location:no;menubar:no;help:no', function GotoPageAfterClose(pageid){if(pageid == 'hold') {STSNavigate(unescape(decodeURI('{SiteUrl}'))+'/_layouts/hold.aspx?ID={ItemId}&List={ListId}'); return false;} if(pageid == 'audit') {STSNavigate(unescape(decodeURI('{SiteUrl}'))+'/_layouts/Reporting.aspx?Category=Auditing&backtype=item&ID={ItemId}&List={ListId}'); return false;} if(pageid == 'config') {STSNavigate(unescape(decodeURI('{SiteUrl}'))+'/_layouts/expirationconfig.aspx?ID={ItemId}&List={ListId}'); return false;}}, null); return false; 0x0 0x1 ContentType 0x01 898 Edit in Browser /_layouts/images/icxddoc.gif /blogs/eshupps/_layouts/formserver.aspx?XsnLocation={ItemUrl}&OpenIn=Browser&Source={Source} 0x0 0x1 FileType xsn 255 Edit in Browser /_layouts/images/icxddoc.gif /blogs/eshupps/_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser&Source={Source} 0x0 0x1 ProgId InfoPath.Document 255 Edit in Browser /_layouts/images/icxddoc.gif /blogs/eshupps/_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser&Source={Source} 0x0 0x1 ProgId InfoPath.Document.2 255 Edit in Browser /_layouts/images/icxddoc.gif /blogs/eshupps/_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser&Source={Source} 0x0 0x1 ProgId InfoPath.Document.3 255 Edit in Browser /_layouts/images/icxddoc.gif /blogs/eshupps/_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser&Source={Source} 0x0 0x1 ProgId InfoPath.Document.4 255 |
|
|
|
|
|
|
|
|
|
 Mary Cummins, Animal Advocates, Cummins Real Estate Services Mary Cummins, Animal Advocates, Cummins Real Estate Services Mary Cummins, Animal Advocates, Cummins Real Estate Services Mary Cummins, Animal Advocates, Cummins Real Estate Services Mary Cummins, Animal Advocates, Cummins Real Estate Services Mary Cummins, Animal Advocates, Cummins Real Estate Services Mary Cummins, Animal Advocates, Cummins Real Estate Services Mary Cummins, Animal Advocates, Cummins Real Estate Services Mary Cummins, Animal Advocates, Cummins Real Estate Services
|
|
|
|