Encrypted JavaScript in ASP.NET MVC Core

To complete the Data URI saga, here is another technique that you may find interesting: this time, it’s about encrypting JavaScript contents, so as to make them more difficult to tamper with.

Data URIs can be used for serving any content that would normally come from an external resource, such as a JavaScript/CSS file or an image. What we will do here is, using ASP.NET MVC Core’s Tag Helpers, we get hold of the JavaScript that is defined inside a SCRIPT tag and we turn into a Data URI. Pretty simple, yet the result is also pretty cool!

Show me the code, I hear you say:

[HtmlTargetElement("script")]

[HtmlTargetElement("script", Attributes = "asp-encrypt")]

[HtmlTargetElement("script", Attributes = "asp-src-include")]

[HtmlTargetElement("script", Attributes = "asp-src-exclude")]

[HtmlTargetElement("script", Attributes = "asp-fallback-src")]

[HtmlTargetElement("script", Attributes = "asp-fallback-src-include")]

[HtmlTargetElement("script", Attributes = "asp-fallback-src-exclude")]

[HtmlTargetElement("script", Attributes = "asp-fallback-test")]

[HtmlTargetElement("script", Attributes = "asp-append-version")]

public class InlineScriptTagHelper : ScriptTagHelper

{

    public InlineScriptTagHelper(ILogger<ScriptTagHelper> logger, IHostingEnvironment hostingEnvironment,

        IMemoryCache cache, IHtmlEncoder htmlEncoder, IJavaScriptStringEncoder javaScriptEncoder,

        IUrlHelper urlHelper) : base(logger, hostingEnvironment, cache, htmlEncoder, javaScriptEncoder, urlHelper)

    {

    }

 

    [HtmlAttributeName("asp-encrypt")]

    public bool Encrypt { get; set; }

 

    public override void Process(TagHelperContext context, TagHelperOutput output)

    {

        if ((this.Encrypt == true) && (string.IsNullOrWhiteSpace(this.Src) == true))

        {

            var content = output.GetChildContentAsync().GetAwaiter().GetResult().GetContent();

            var encryptedContent = Convert.ToBase64String(Encoding.ASCII.GetBytes(content));

            var script = $"data:text/javascript;base64,{encryptedContent}";

 

            output.Attributes.Add("src", script);

            output.Content.Clear();

        }

 

        base.Process(context, output);

    }

}

You can see that this tag helper inherits from ScriptTagHelper, which is the OOTB class that handles SCRIPT tags. Because of this inheritance, we need to add all the HtmlTargetElementAttributes, so that all the rules get applied properly. For this one, we need to add an extra rule, which forces the SCRIPT tag to have an asp-encrypt attribute.

So, say you have this in your Razor view:

<script asp-encrypt="true">
   1:  

   2:     window.alert('hello, world, from an encrypted script!');

   3:  

</script>

What you’ll end up with in the rendered page is this:

<script src="data:text/javascript;base64,DQoNCiAgICB3aW5kb3cuYWxlcnQoJ2hlbGxvLCB3b3JsZCwgZnJvbSBhbiBlbmNyeXB0ZWQgc2NyaXB0IScpOw0KDQo="></script>

Cool, wouldn’t you say? Of course, no JavaScript that runs on the client can ever be 100% secure, and you should always keep that in mind. But for some purposes, it does pretty well! Winking smile

Fluent Validation in JavaScript

A recent discussion with my colleagues about fluent validation in JavaScript made me realize that I didn’t know of any such library. I take it for granted that some may exist, but I have never actually used one. To be clear, I mean a validation library that I can use in unit tests, for asserting conditions. Because I had a free Saturday morning, I decided to write my own!

Nothing fancy, just a couple of methods for dealing with common validations. It can be used in both browsers and in Node.js apps. Several validations can be chained and it any fails, an error is thrown. It goes like this:

/**

 * Builds a new Validation object

 * @param {object} obj The object to validate

 */

function Validation(obj) {    

    this.obj = obj;

    this.negated = false;

    this.reporting(null);

}

 

/**

 * Try to find the first argument of a given type

 * @param {array} args An array of arguments

 * @param {string} type The type to find

 * @returns {object} The first argument that matches the given type, or undefined

 */

function findFirstOfType(args, type) {

    for (var i = 0; i < args.length; ++i) {

        if (typeof args[i] === type) {

            return args[i];

        }

    }

    return undefined;

}

 

/**

 * Either returns the first argument as an array or the arguments implicit parameter

 * @param {array} args A possibly array parameter

 * @param {array} array An array of arguments

 * @returns {array} An array of arguments

 */

function getArguments(args, array) {

    var newArray = args;

    

    if (!(args instanceof Array))

    {

        newArray = [ args ];

        for (var i = 1; i < array.length; ++i) {

            newArray.push(array[i]);

        }

    }

    

    return newArray;

}

 

/**

 * Throws an exception in case of an error

 * @param {boolean} error An error flag

 * @param {string} msg An error message

 */

Validation.prototype.assert = function(error, msg) {

    if (error != this.negated) {

        this.report(msg);

    }

};

 

/**

 * Changes the reporting function in case of a validation error. The default is to throw an Error

 * @param {function} fn A reporting function

 * @returns {object} The Validation object

 */

Validation.prototype.reporting = function(fn) {

    if ((!fn) || (typeof fn !== 'function')) {

        fn = function(msg) {

            throw new Error(msg);

        };

    }

    

    this.report = fn;

    return this;

};

 

/**

 * Uses an external validation function

 * @param {function} fn A validation function

 * @param {string} msg An optional error message

 */

Validation.prototype.isValid = function(fn, msg) {

    var self = this;

    msg = msg || 'Validation failed: custom validation function';

    var error = (fn(self.obj) !== true);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is one of a set of passed values

 * @param {array} args An optional array of arguments

 */

Validation.prototype.isOneOf = function(args) {

    var self = this;

    var msg = 'Validation failed: objects do not match';

    var error = arguments.length > 0;

    args = getArguments(args, arguments);

    

    for (var i = 0; i < args.length; ++i) {

        if (self.obj == args[i]) {

            error = false;

            break;

        }

    }

    

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is not in a set of passed values

 * @param {array} args An optional array of arguments

 */

Validation.prototype.isNoneOf = function(args) {

    var self = this;

    var msg = 'Validation failed: objects do not match';

    var error = false;

    args = getArguments(args, arguments);

        

    for (var i = 0; i < args.length; ++i) {

        if (self.obj == args[i]) {

            error = true;

            break;

        }

    }

    

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate contains a supplied value

 * @param {object} value A value to find

 * @param {string} msg An optional error message

 */

Validation.prototype.contains = function(value, msg) {

    var self = this;

    msg = msg || 'Validation failed: object does not contain target';

    var error = self.obj.length != 0;

    

    for (var i = 0; i < self.obj.length; ++i) {

        if (self.obj[i] == value) {

            error = false;

            break;

        }

    }

    

    this.assert(error, msg);

    return this;    

};

 

/**

 * Checks if the value to validate is equal to a supplied value

 * @param {object} value A value to compare against

 * @param {object} arg1 An optional argument

 * @param {object} arg2 An optional argument

 */

Validation.prototype.isEqualTo = function(value, arg1, arg2) {

    var self = this;

    var caseInsensitive = findFirstOfType([arg1, arg2], 'boolean') || false;

    var msg = findFirstOfType([arg1, arg2], 'string') || 'Validation failed: objects do not match';

    var left = self.obj.toString();

    var right = value.toString();

            

    if (caseInsensitive) {

        left = left.toLowerCase();

        right = right.toLowerCase();

    }

 

    var error = (left != right);

    

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is a string

 * @param {string} msg An optional error message

 */

Validation.prototype.isString = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not a string';

    var error = ((typeof self.obj !== 'string') && (self.obj.toString() != obj));

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is neither null nor a whitespace string

 * @param {string} msg An optional error message

 */

Validation.prototype.isNotNullOrWhitespace = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is null or whitespace';

    var error = ((self.obj === null) || (self.obj.toString().trim().length == 0));

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is a number

 * @param {string} msg An optional error message

 */

Validation.prototype.isNumber = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not a number';

    var error = ((typeof self.obj !== 'number') && (Number(self.obj) != self.obj));

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is a positive number

 * @param {string} msg An optional error message

 */

Validation.prototype.isPositive = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not a number';

    var error = (Number(self.obj) <= 0);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is a negative number

 * @param {string} msg An optional error message

 */

Validation.prototype.isNegative = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not a number';

    var error = (Number(self.obj) >= 0);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is an odd number

 * @param {string} msg An optional error message

 */

Validation.prototype.isOdd = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not odd';

    var error = (Number(self.obj) % 2 === 0);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is an even number

 * @param {string} msg An optional error message

 */

Validation.prototype.isEven = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not even';

    var error = (Number(self.obj) % 2 !== 0);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is a finite number

 * @param {string} msg An optional error message

 */

Validation.prototype.isFinite = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is infinite';

    var error = !isFinite(self.obj);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is a function

 * @param {string} msg An optional error message

 */

Validation.prototype.isFunction = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not a function';

    var error = (typeof self.obj !== 'function');

    this.assert(error, msg);

    return this;    

};

 

/**

 * Checks if the value to validate is a boolean

 * @param {string} msg An optional error message

 */

Validation.prototype.isBoolean = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not a boolean';

    var error = (typeof self.obj !== 'boolean');

    this.assert(error, msg);

    return this;    

};

 

/**

 * Checks if the value to validate is defined

 * @param {string} msg An optional error message

 */

Validation.prototype.isDefined = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is undefined';

    var error = (self.obj === undefined);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is null

 * @param {string} msg An optional error message

 */

Validation.prototype.isNull = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not null';

    var error = (self.obj !== null);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is of a primitive type

 * @param {string} msg An optional error message

 */

Validation.prototype.isPrimitive = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not of a primitive type';

    var type = typeof self.obj;

    var error = ((type !== 'number') && (type !== 'string') && (type !== 'boolean'));

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is an array

 * @param {string} msg An optional error message

 */

Validation.prototype.isArray = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not an array';

    var error = !Array.isArray(self.obj);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate matches a regular expression

 * @param {string} regex A regular expression

 * @param {string} msg An optional error message

 */

Validation.prototype.isMatch = function(regex, msg) {

    var self = this;

    msg = msg || 'Validation failed: object does not match regular expression';

    var error = !(new RegExp(regex).test(self.obj.toString()));

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is valid JSON

 * @param {string} msg An optional error message

 */

Validation.prototype.isJSON = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not valid JSON';

    var error = false;

    try

    {

        error = (typeof JSON.parse(self.obj) !== 'object');

    }

    catch(e)

    {

        error = true;

    }

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate has a given length

 * @param {number} max The maximum length

 * @param {object} arg1 An optional argument

 * @param {object} arg2 An optional argument

 */

Validation.prototype.hasLength = function(max, arg1, arg2) {

    var self = this;

    var msg = findFirstOfType([arg1, arg2], 'string') || 'Validation failed: length does not fall between the given values';    

    var min = findFirstOfType([arg1, arg2], 'number') || 0;        

    var str = self.obj.toString();

    var error = str.length > max;

 

    if (!error) {

        error = (str.length < min);

    }

    

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is a Promise

 * @param {string} msg An optional error message

 */

Validation.prototype.isPromise = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not a promise';

    var error = ((typeof Promise === 'undefined') || !(self.obj instanceof Promise));

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is a Date

 * @param {string} msg An optional error message

 */

Validation.prototype.isDate = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not a date';

    var error = !(self.obj instanceof Date);

    this.assert(error, msg);

    return this;

};

 

/**

 * Checks if the value to validate is an Error

 * @param {string} msg An optional error message

 */

Validation.prototype.isError = function(msg) {

    var self = this;

    msg = msg || 'Validation failed: object is not an error';

    var error = !(self.obj instanceof Error);

    this.assert(error, msg);

    return this;

};

 

/**

 * Negates the validation logic

 * @returns {object} The Validation object

 */

Validation.prototype.not = function() {

    this.negated = !this.negated;

    return this;

};

 

/**

 * Validates an object

 * @returns {object} The Validation object

 */

Object.prototype.validate = function() {

    return Validation.validate(this);

};

 

Validation.validate = function(obj) {

    var val = new Validation(obj);

    return val;

};

 

if (typeof module !== 'undefined') {

    module.exports = Validation;

}

It’s usage is simple, as would be expected from a fluent library:

//the object to validate

var obj = '1';

 

//fire a bunch of validations - not all make sense

Validation

    .validate(obj)

    .isDefined()

    .isPrimitive()

    .isValid(function(x) { return true })

    .contains('1')

    .isOdd()

    .isOneOf('1', '2', '3')

    .isNoneOf('4', '5', '6')

    .isEqualTo(1)

    .isNotNullOrWhitespace()

    .isString()

    .isNumber()

    .hasLength(2, 1)

    .isMatch('\\d+')

    .isPositive();

The included checks are:

  • isValid: takes a JavaScript function to validate the object;
  • isOneOf, isNoneOf: check to see if the target object is contained/is not contained in a set of values;
  • contains: checks if the object to validate – possibly an array – contains in it a given value;
  • isEqualTo: performs a string comparison, optionally case-insensitive;
  • isString, isNumber, isFunction, isArray, isDate, isJSON, isPromise, isError, isBoolean, isPrimitive: what you might expect from the names;
  • isNotNullOrWhitespace: whether the target object’s string representation is neither null or white spaces;
  • isDefined, isNull: checks if the target is defined (not undefined) or null;
  • isMatch: checks if the object matches a given regular expression;
  • hasLength: validates the object’s length (maximum and optional minimum size);
  • isOdd, isEven, isPositive, isNegative, isFinite: whether the object is odd, even, positive, negative or finite.

All of the checks can be negated as well by using not:

Validation

    .validate(obj)

    .not()

    .isNull()

    .isEven()

    .isNegative()

    .isPromise()

    .isArray()

    .isFunction()

    .isDate()

    .isError()

    .isJSON();

And most can also take an optional message, that will be used instead of the default one:

Validation

    .validate(obj)

    .isNull('Hey, I'm null!');

The actual bootstrapping Validation “class” is actually optional, because of the Object prototype extension method validate:

obj

    .validate()

    .isDefined();

The checks that accept array parameters (isOneOf, isNoneOf) can either take a single array argument or an undefined number or arguments (notice the difference):

obj

    .validate()

    .isOneOf([ 1, 2, 3])

    .isNoneOf(4, 5, 6);

Finally, the actual outcome of a failed validation can be controlled through the reporting function, for example, if you prefer to log to the console instead of raising an exception:

Validation

    .validate(obj)

    .reporting(function(msg) { console.log(msg) })

    .isDefined();

The code is available in GitHub: https://github.com/rjperes/FluentValidationJS/. Please use it as you like, and let me know if you face any issues. By all means, improve it as you like, but let me hear about it! Winking smile

ASP.NET 5 Node Services

At the MVP Global Summit, Steven Sanderson (@stevensanderson) presented a Microsoft project he was working on: Node Services. In a nutshell, this is an integration of ASP.NET 5 and Node.js, it makes it possible to call a Node.js function from ASP.NET. One of its possible usages is to use Node.js to compile AngularJS directives or ReactJS JSX files, and for that reason, there are two modules built on top of Node Services just for that purpose (code available at GitHub and NuGet, here and here).

Disclaimer: this is still in early stage, and is likely to change!

So, after you install the NuGet package using the now familiar syntax:

image

You need to register the Node Services it in Startup.cs’s ConfigureServices method:

var app = services.Single(x => x.ServiceType == typeof (IApplicationEnvironment)).ImplementationInstance as IApplicationEnvironment;

 

services.AddSingleton<INodeServices>(new Func<IServiceProvider, INodeServices>(sp => Configuration.CreateNodeServices(NodeHostingModel.Http, app.ApplicationBasePath)));

Now, there are two ways by which ASP.NET can communicate with the Node.js host:

  • HTTP (NodeHostingModel.Http): a local service is started which waits for JSON requests and routes them to Node.js;
  • Interprocess communication (NodeHostingModel.InputOutputStream): a direct stream with the Node.js hosting process; much faster than the HTTP version, but also less reliable.

Creating a Node.js module is easy; let’s add a .JS file to the project:

module.exports = function (cb, a, b)

{

    a = parseInt(a);

    b = parseInt(b);

 

    return cb(null, (a + b).toString());

};

For simplicity’s sake, we are doing a very simple operation: just adding two numbers, but in a real-life scenario you can require any other Node.js modules and do any arbitrarily complex operations. You need to be aware of a couple of things:

  • The first parameter to the exported function must be a callback function; this will be used for returning the result;
  • Each parameter must either be a string or a JSON object;
  • The result – second parameter to the callback function – also needs to be a string or JSON.

And how do you call this? You just need a reference to the registered instance of INodeService, and on it you call a module asynchronously, passing it some parameters:

var node = this.Resolver.GetService(typeof(INodeServices)) as INodeServices;

var result = await node.Invoke<string>("sum.js", "1", "2");    //"3"

You can also specify the module and function name explicitly, if you export it from the .JS file (not in this example):

var result = await node.InvokeExport<string>("sum.js", "sum", "1", "2");

Happy Noding! Winking smile

Persisting SignalR Connections Across Page Reloads

I recently had the need to keep a SignalR connection even if the page would reload. As far as I know, this cannot be done out of the box, either with hubs or persistent connections. I looked it up, but could find no solid solution, so here is my solution!

First, we need to create a “session id” that is to be stored at the browser side. Mind you, this is not an ASP.NET session, nor a SignalR connection id, it’s something that uniquely identifies a session. To maintain sessions we normally use cookies, but my solution uses instead HTML5 session storage. I had to generate a session id, and there were several solutions available, from pseudo-GUIDs, to the SignalR connection id, but I ultimately decided to use the timestamp; here is it:

function getSessionId()

{

    var sessionId = window.sessionStorage.sessionId;

    

    if (!sessionId)

    {

        sessionId = window.sessionStorage.sessionId = Date.now();

    }

    

    return sessionId;

}

As you can see, this function first checks to see if the session id was created, by inspecting the sessionStorage object, and, if not, sets it.

Next, we need to have SignalR pass this session id on every request to the server. For that, I used $.connection.hub.qs, the query string parameters object:

$.connection.hub.qs = { SessionId: getSessionId() };

$.connection.hub.start().done(function ()

{

    //connection started

});

Moving on to the server-side, I used a static collection to store, for each session id, each SignalR connection id associated with it – one for each page request. The reasoning is, each page reload generates a new SignalR connection id, but the session id is always kept:

public sealed class NotificationHub : Hub

{

    internal const string SessionId = "SessionId";

 

    public static readonly ConcurrentDictionary<string, HashSet<string>> sessions = new ConcurrentDictionary<string, HashSet<string>>();

 

    public static IEnumerable<string> GetAllConnectionIds(string connectionId)

    {

        foreach (var session in sessions)

        {

            if (session.Value.Contains(connectionId) == true)

            {

                return session.Value;

            }

        }

 

        return Enumerable.Empty<string>();

    }

 

    public override Task OnReconnected()

    {

        this.EnsureGroups();

 

        return base.OnReconnected();

    }

 

    public override Task OnConnected()

    {

        this.EnsureGroups();

 

        return base.OnConnected();

    }

 

    private void EnsureGroups()

    {

        var connectionIds = null as HashSet<string>;

        var sessionId = this.Context.QueryString[SessionId];

        var connectionId = this.Context.ConnectionId;

 

        if (sessions.TryGetValue(sessionId, out connectionIds) == false)

        {

            connectionIds = sessions[sessionId] = new HashSet<string>();

        }

 

        connectionIds.Add(connectionId);

    }

}

As you can see, both on OnConnected as in OnReconnected, I add the current connection id to the collection (ConcurrentDictionary<TKey, TValue> to allow multiple concurrent accesses) indexed by the session id that I sent in the SignalR query string. Then, I have a method that looks in the collection for all connection id entries that are siblings of a given connection id. If more than one exists, it means that the page has reloaded, otherwise, there will be a one-to-one match between connection ids and session ids.

The final step is to broadcast a message to all the sibling connection ids – a waste of time because only one is still possibly active, but since we have no way of knowing, it has to be this way:

[HttpGet]

[Route("notify/{connectionId}/{message}")]

public IHttpActionResult Notify(string connectionId, string message)

{

    var context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();

    var connectionIds = NotificationHub.GetAllConnectionIds(connectionId).ToList();

 

    context.Clients.Clients(connectionIds).MessageReceived(message);

 

    return this.Ok();

}

This Web API action method will get the context for our hub (NotificationHub), look up all of the sibling connection ids for the passed one, and then broadcast a message to all clients identified by these connection ids. It’s a way to send messages from outside of a page into a hub’s clients

Problems with this approach:

  • All tabs will get the same session id, but that also happens with cookies;
  • Although unlikely, it may be possible for two clients to get the same session id, which I implemented as the current timestamp; an easy fix would be, for example, to use a pseudo-GUID, the server-side session id, or even the SignalR connection id;
  • If the page reloads several times, there will be several connection id entries for the same session id – which will be kept throughout all reloads; no easy way to get around this, except possibly using some cache with expiration mechanism.

And that’s it. Enjoy!

Querying SharePoint

Introduction

SharePoint, being a content management system, of course, offers a couple of ways to query its contents programmatically. Here we will explore some of these options.

Web Parts

First, of course, there are the web parts. These allow us to configure queries visually on a page. The most important web parts are:

ContentByQueryWebPart: use this for simple queries that do not return much contents, on a single site collection. Can be used in SharePoint Online. Can only be customized through XSLT.

ContentBySearchWebPart (introduced in SharePoint 2013): more powerful, but does not exist in SharePoint Online. Can handle more complex queries that can span multiple site collections and multiple levels of sorting. Can only be customized through HTML and JavaScript templates. Results can be refined.

XsltListViewWebPart/DataFormWebPart: can be used to view of a specific list. The display can be totally configured. XsltListViewWebPart is a bit more powerful.

ListViewByQuery: requires that you pass an SPList and a SPQuery instance. Can only display pre-defined views.

APIs

There are several APIs for querying, either directly or using the search index.

SPQuery: can only be applied to a single list:

var query = new SPQuery();

query.Query = "<Where><Eq><FieldRef Name='Title'/><Value Type='Text'>Bla Bla</Value></Eq></Where>";

 

var list = site.RootWeb.Lists["Some List"];

 

var table = list.GetItems(query).GetDataTable();

SPSiteDataQuery: can be applied to several lists across a site collection. Has some issues, for example, won’t return values for multi-value fields:

var query = new SPSiteDataQuery();

query.Lists = "<List Name='Tasks'/>";

query.Query = "<Where><Eq><FieldRef Name='Title'/><Value Type='Text'>Bla Bla</Value></Eq></Where>";

query.Webs = "<Web Scope='SiteCollection'/>";

 

var table = site.RootWeb.GetSiteData(query);

KeywordQuery: uses the search index to search for keywords, so it requires that contents are indexed beforehand, and the search service is functioning:

using (var query = new KeywordQuery(site))

{

    query.QueryText = "Bla Bla";

    query.ResultsProvider = SearchProvider.Default;

 

    var searchExecutor = new SearchExecutor();

 

    var resultTableCollection = searchExecutor.ExecuteQuery(query);

 

    var searchResult = resultTableCollection.Filter("TableType", KnownTableTypes.RelevantResults).Single();

 

    var table = new DataTable();

    table.TableName = "Result";

    table.Load(searchResult, LoadOption.OverwriteChanges);

}

FullTextSqlQuery: uses SharePoint Search SQL to execute queries, which makes it generally more powerful than KeywordQuery:

using (var query = new FullTextSqlQuery(site))

{

    query.QueryText = "SELECT * FROM scope() WHERE Title = 'Teste'";

    query.ResultsProvider = SearchProvider.Default;

 

    var searchExecutor = new SearchExecutor();

 

    var resultTableCollection = searchExecutor.ExecuteQuery(query);

 

    var searchResult = resultTableCollection.Filter("TableType", KnownTableTypes.RelevantResults).Single();

 

    var table = new DataTable();

    table.TableName = "Result";

    table.Load(searchResult, LoadOption.OverwriteChanges);

}

CrossListQueryInfo and CrossListQueryCache: performs queries in a single site collection but multiple sites, with optional audience targeting. CrossListQueryCache caches the results for a period of time:

var crossListQueryInfo = new CrossListQueryInfo();

crossListQueryInfo.Query = "<Where><Eq><FieldRef Name='Title'/><Value Type='Text'>Bla Bla</Value></Eq></Where>";

crossListQueryInfo.Lists = "<List Name='Some List'/>";

crossListQueryInfo.Webs = "<Webs Scope='SiteCollection' />";

crossListQueryInfo.UseCache = true;

 

var crossListQueryCache = new CrossListQueryCache(crossListQueryInfo);

 

var table = crossListQueryCache.GetSiteData(site.RootWeb);

Client KeywordQuery: this is the client counterpart to KeywordQuery. It uses the SharePoint Client Components, or Client Side Object Model (CSOM), which means, it accesses SharePoint through its web services. It is basically similar to the server KeywordQuery, but can be used from a remote machine:

var keywordQuery = new Microsoft.SharePoint.Client.Search.Query.KeywordQuery(ctx);

keywordQuery.QueryText = "SharePoint";

 

var searchExecutor = new Microsoft.SharePoint.Client.Search.Query.SearchExecutor(ctx);

 

var results = searchExecutor.ExecuteQuery(keywordQuery);

 

ctx.ExecuteQuery();

 

var searchResult = results.Value.Where(x => x.TableType == KnownTableTypes.RelevantResults.ToString()).Single();

Web Services

Search.asmx: SOAP web service that takes a SharePoint SQL query. Part of the SharePoint Foundation 2010 Web Services, now somewhat obsolete.

Lists.asmx: Another SOAP web service that can return and update items in a list.

ListData.svc: WCF Data Service REST/OData that can be used for querying (including OData queries) or modifying contents of lists.

SharePoint 2013 REST Services: new REST/OData web services introduced in SharePoint 2013. Includes _api/web, _api/search, _api/web/lists, for searches, web or list operations.

SharePoint Foundation RPC Protocol

The SharePoint Foundation RPC Protocol, now obsolete, this allowed querying, exporting contents and performing a number of other operations through the OWSSVR.DLL handler. Although almost unused nowadays, still offers the only out of the box way to, for example, export a list in XML format.

JavaScript

In SharePoint 2010 the JavaScript Side Object Model (JSOM) was introduced and in version 2013 it was enhanced. It is now possible to do anything that the SharePoint Client API allows.

Conclusion

You see, lots of ways to get contents from SharePoint, as usual. Make sure you chose the one that best suits your needs.

References

SharePoint 2013 .NET Server, CSOM, JSOM, and REST API index

When to use the Content Query Web Part or the Content Search Web Part in SharePoint

Choose the right API set in SharePoint 2013

Use OData query operations in SharePoint REST requests

SharePoint Server 2013 Client Components SDK

SharePoint Search SQL Syntax Reference

ASP.NET Web Forms Prompt Validator

For those still using Web Forms and Microsoft’s validation framework, like yours truly – and I know you’re out there! -, it is very easy to implement custom validation by leveraging the CustomValidator control. It allows us to specify both a client-side validation JavaScript function and a server-side validation event handler.

In the past, I had to ask for confirmation before a form was actually submitted; the native way to ask for confirmation is through the browser’s confirm function, which basically displays a user-supplied message and two buttons, OK and Cancel. I wrapped it in a custom reusable validation control, which I am providing here:

   1: [DefaultProperty("PromptMessage")]

   2: public sealed class PromptValidator : CustomValidator

   3: {

   4:     [DefaultValue("")]

   5:     public String PromptMessage { get; set; }

   6:  

   7:     protected override void OnPreRender(EventArgs e)

   8:     {

   9:         var message = String.Concat("\"", this.PromptMessage, "\"");

  10:  

  11:         if ((this.PromptMessage.Contains("{0}") == true) && (this.ControlToValidate != String.Empty))

  12:         {

  13:             message = String.Concat("String.format(\"", this.PromptMessage, "\", args.Value)");

  14:         }

  15:  

  16:         this.ClientValidationFunction = String.Concat("new Function('sender', 'args', 'args.IsValid = confirm(", message, ")')");

  17:         this.EnableClientScript = true;

  18:  

  19:         base.OnPreRender(e);

  20:     }

  21: }

A sample usage without any target control might be:

   1: <web:PromptValidator runat="server" PromptMessage="Do you want to submit your data?" ErrorMessage="!"/>

And if you want to specifically validate a control’s value:

   1: <web:PromptValidator runat="server" PromptMessage="Do you want to accept {0}?" ErrorMessage="!" ControlToValidate="text" ValidateEmptyText="true"/>

When submitting your form, you will get a confirmation prompt similar to this (Chrome):

image