Category Archives: ASP.NET

.Net client SDK for WebApi

Generating a strongly typed client SDK for a WebApi can be feasible and easy now via Swagger metadata and Azure SDK tooling in Visual Studio.

Even if your WebApi is not hosted as an Azure API App. You still can leverage the Azure API App client tool integrated in Visual Studio (Via Azure SDK tooling)

Here are the needed steps, to get your WebApi .Net Client SDK generated:

  1. Generate a Swagger metadata (.json file) for WebApi [If you don’t have one already] May be using Swashbuckle is the easiest way.
  2. Download and install Azure SDK
  3. From Visual Studio, right-click on the project you wish to create your Client SDK in,  then select “Add” => “Azure API App Client…” and select the Swagger metadata file (.json) to create your Client SDK.

SharePoint 2013 Razor View WebPart

Download SPRazorView

Razor syntax is a very easy, powerful and less verbose format. This is a proof of concept to utilize this powerful format to be hosted in a SharePoint WebPart!

The idea is very simple; to have a SharePoint WebPart that can host a razor view (.cshtml, .vbhtml) and render its output.

We can compile razor files (.cshtml, .vbhtml) during the runtime using System.Web.Compilation.BuildManager. This will require adding cshtml and vbhtml build providers to the application web.config file.

<add extension=".cshtml" type="System.Web.WebPages.Razor.RazorBuildProvider, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add extension=".vbhtml" type="System.Web.WebPages.Razor.RazorBuildProvider, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

So, now all we need is to use the BuildManager to compile a razor file and render it in the normal rendering method of the SharePoint WebPart.

The razor file can be saved in SharePoint content database, and can be edited from SharePoint designer using Visual Studio or any text editor.

The render method of the WebPart will look like the following:

protected override void Render(HtmlTextWriter writer)
{
    // Check that there is a file specified
    if (string.IsNullOrWhiteSpace(RazorViewVirtualPath))
    {
        writer.Write(string.Format("Please specify a razor file to render in the WebPart settings. <a id="" href="\&quot;javascript:ShowToolPane2Wrapper('Edit',">Open tool pane</a>"));
        return;
    }
    Type razorViewType;
    try
    {
        // Compile the razor file into a type (This should be cached to improve the performance)
        razorViewType = BuildManager.GetCompiledType(RazorViewVirtualPath);
        if (null == razorViewType)
        {
            throw new Exception("Unable to compile the razor view.");
        }
    }
    catch (Exception e)
    {
        writer.Write(string.Format("Something went wrong while compiling the razor view at {0}: {1}", RazorViewVirtualPath, e.Message));
        return;
    }
    // Create an object of this type
    var page = Activator.CreateInstance(razorViewType) as WebPageBase;
    page.ExecutePageHierarchy(new WebPageContext(new HttpContextWrapper(this.Context), page, null), writer, page);

    base.Render(writer);
}

That’s all! for the concept. The compilation of Razor is working now but it gives a compilation error!

It was required to add System.Web.WebPages to the assemblies in the application web.config to overcome these compilation errors during the razor file compilation.

<add assembly="System.Web.WebPages, Version=3.0.0.0,Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

But adding the System.Web.WebPages assembly automatically adds an HttpModule to the pipeline which in turn required me to implement a custom VirtualPathProvider to handle some special cases that was making the default SPVirtualPathProvider crashing!

So, I wrote this custom VirtualPathProvider to handle those special cases as follows:

internal sealed class SPRazorViewVirtualPathProvider : VirtualPathProvider
{
    public override string CombineVirtualPaths(string basePath, string relativePath)
    {
        return Previous.CombineVirtualPaths(basePath, relativePath);
    }
    public override string GetCacheKey(string virtualPath)
    {
        return Previous.GetCacheKey(virtualPath);
    }
    public override VirtualDirectory GetDirectory(string virtualDir)
    {
        return Previous.GetDirectory(virtualDir);
    }
    public override VirtualFile GetFile(string virtualPath)
    {
        return Previous.GetFile(virtualPath);
    }
    public override string GetFileHash(string virtualPath, System.Collections.IEnumerable virtualPathDependencies)
    {
        return Previous.GetFileHash(virtualPath, virtualPathDependencies);
    }
    public override bool FileExists(string virtualPath)
    {
        // This is a workaround for System.Web.WebPages module initialization checking for precompiledapp which will 
        // not work with SPVirtualPathProvider
        if (virtualPath.ToLower().EndsWith("precompiledapp.config"))
            return false;
        return Previous.FileExists(virtualPath);
    }
    public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, System.DateTime utcStart)
    {
        if (virtualPath.ToLower().StartsWith("~/_appstart."))
            virtualPath = virtualPath.TrimStart('~');
        return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
    }
    public override bool DirectoryExists(string virtualDir)
    {
        try
        {
            return Previous.DirectoryExists(virtualDir);
        }
        catch (Exception e)
        {
            return false;
        }
    }
}

I registered this SPRazorViewVirtualPathProvider to the HostingEnvironment via a custom HttpModule:

internal sealed class SPRazorViewHttpModule : IHttpModule
{
    private static bool razorViewVirtualPathProviderInitialized = false;
    private static object _lock = new object();
    public void Init(HttpApplication context)
    {
        if (razorViewVirtualPathProviderInitialized)
            return;
        lock (_lock)
        {
            var razorViewVirtualPathProvider = new SPRazorViewVirtualPathProvider();
            HostingEnvironment.RegisterVirtualPathProvider(razorViewVirtualPathProvider);
            razorViewVirtualPathProviderInitialized = true;
        }
    }
    public void Dispose()
    {
    }
}

To add the SPRazorViewHttpModule to the application pipeline, I used the PreApplicationStartMethod assembly attribute to register the module via DynamicModuleUtility.RegisterModule during the application starting.

I added the solution assembly to the web.config assemblies:

<add assembly="SPRazorView, Version=1.0.0.0, Culture=neutral, PublicKeyToken=64bd6e273698a7b0" />

I packaged the whole solution along with all the required web.config modification in a SharePoint Package (WSP) to make things easier.

Download SPRazorView

Paginating SharePoint List Items

Paginating SharePoint list items is not a straight forward task. You have to do some plumbing to make it work. especially, if you have sorting and filtering applied to the query you want to get your items with.

This is my trial to make the task easier:
SPPagedListItemsRetriever is a class that we need to instantiate by specifying the SPList we need to work with, and SPQuery we will execute.

SPPagedListItemsRetriever pagedItemsRetriever = new SPPagedListItemsRetriever(list, query);

Then, we have 2 public methods:

public SPListItem[] GetItems(int? startIndex, int? maxRowsCount); 
public int GetTotalItemsCount();

The drawback of the used technique in this implementation, is that it always get the items from the beginning! So, if you are asking to get the items starting from item 200 and with page size 10, It will get the first 199 items and throw them away, then begin getting the required items from 200 to 210.

Here is the complete post of the code:

/// <summary>
/// Retrieves paginated items of SPList with the specified SPQuery.
/// </summary>
public class SPPagedListItemsRetriever
{
    private SPQuery _query;
    private SPList _list;
    private const int MaxRowLimit = 2000;
    private static SPListItemCollectionPosition _emptySPListItemCollectionPosition = new SPListItemCollectionPosition(string.Empty);

    /// <summary>
    /// Constructs a new instance of SPPagedListItemsRetriever
    /// </summary>
    /// <param name="list" />The list to get the items from
    /// <param name="query" />The query by which the items should be retrieved
    public SPPagedListItemsRetriever(SPList list, SPQuery query)
    {
        _list = list;
        _query = query;
    }

    /// <summary>
    /// Get the items of the list with the specified query begining from a specified startIndex and with maxRowsCount (PageSize)
    /// </summary>
    /// <param name="startIndex" />
    /// <param name="maxRowsCount" />
    /// <returns></returns>
    public SPListItem[] GetItems(int? startIndex, int? maxRowsCount)
    {
        SPListItemCollectionPosition listItemCollectionPosition = null;
        uint actualStartIndex = startIndex.HasValue ? (uint)startIndex.Value : 0;
        //If we need items beginning from a specific index (greater that 0, the first one)
        //Create a dummy query to begin getting the items from the first one (0) till we reach the specified startIndex
        if (actualStartIndex > 0)
        {
            SPQuery dummyQuery = new SPQuery();
            //Change the ViewFields returned from this dummy query to minimal, actually we dont need these items so selelct the ID only to minimize the view fields
            dummyQuery.ViewFields = "<fieldref name='ID'>";
            dummyQuery.Query = _query.Query;

            if (null != _query.Folder)
                dummyQuery.Folder = _query.Folder;

            int gotDummyItems = 0;
            do
            {
                //Minimize the number of items not to exceed the recommended 2000 MaxRowLimit for SPQuery
                dummyQuery.RowLimit = Math.Min((uint)(actualStartIndex - gotDummyItems), MaxRowLimit);
                if (null == listItemCollectionPosition)
                    listItemCollectionPosition = _emptySPListItemCollectionPosition;

                dummyQuery.ListItemCollectionPosition = listItemCollectionPosition;
                SPListItemCollection items = _list.GetItems(dummyQuery); gotDummyItems += items.Count;
                listItemCollectionPosition = items.ListItemCollectionPosition;
            }
            while (gotDummyItems < actualStartIndex && listItemCollectionPosition != null);
        }

        //Now we will get the actual items we need
        SPQuery query = new SPQuery();
        query.Query = _query.Query;
        if (null != _query.Folder)
            query.Folder = _query.Folder;
        query.ViewFields = _query.ViewFields;
        List<splistitem> returnedItemsList = new List<splistitem>();
        uint actualMaxRowCount = maxRowsCount.HasValue ? (uint)maxRowsCount.Value : (uint)_list.ItemCount;

        do
        {
            //Minimize the number of items not to exceed the recommended 2000 MaxRowLimit for SPQuery
            query.RowLimit = Math.Min(actualMaxRowCount, MaxRowLimit);
            if (null == listItemCollectionPosition)
                listItemCollectionPosition = _emptySPListItemCollectionPosition;
            query.ListItemCollectionPosition = listItemCollectionPosition;
            SPListItemCollection listItems = _list.GetItems(query);
            returnedItemsList.AddRange(listItems.Cast<splistitem>().Select(i => i));
            listItemCollectionPosition = listItems.ListItemCollectionPosition;
        }
        while (returnedItemsList.Count < actualMaxRowCount && listItemCollectionPosition != null);

        return returnedItemsList.ToArray();
    }

    /// <summary>
    /// Gets the total items count using the specified query
    /// </summary>
    /// <returns></returns>
    public int GetTotalItemsCount()
    {
        SPQuery query = new SPQuery();
        //Change the ViewFields returned from this dummy query to minimal, actually we dont need these items so selelct the ID only to minimize the view fields
        query.ViewFields = "<fieldref name='ID'>";
        query.Query = _query.Query;
        SPFolder folder = _query.Folder;
        if (null != folder)
            query.Folder = folder;
        return _list.GetItems(query).Count;
    }
}

DotNetNuke Resources keys

In DotnetNuke Localization support, every resource key (in APP_LocalResources or APP_GlobalResources) should end with an extension.
resourceName.Text – Represents a string used for a common purpose
resourceName.Help – Used mostly in conjuncture with the DNN:Label control, which supplies a help icon () and tooltip.
resourceName.Tooltip – Used for a control’s Tooltip property.
resourceName.Action – Used to represent the Title property of a ModuleAction item shown in an actions menu.
resourceName.Confirm – Text that is displayed in a JavaScript’s confirmation dialog (such as a dialog to confirm deleting a module).
resourceName.Error – Text that is displayed as an error message. This might be for a label or, more commonly, for one of the ASP.NET validation controls.
resourceName.Detail – Text that gives more detail about an error message.

If we add a resource key in a resx file of the APP_LocalResources or APP_GlobalResources and didn’t give it an extension (like one of the above or any other one), we cannot get its value back by the DNN Localization logic of the:

DotNetNuke.Services.Localization.Localization.GetString(key, this.LocalResourceFile);

Or:

DotNetNuke.Services.Localization.Localization.GetString(key, <global resource file name>);

Further, if we call the Localization.GetString function with a key without an extension, it will assume that we are asking about the one with the extension “.Text”.
So the call of

Localization.GetString("SomeKey", LocalResourceFile)

is equivalent to:

Localization.GetString("SomeKey.Text", LocalResourceFile)

Localization support in DotNetNuke

In general, DNN disables the ASP.NET localization (the resources files) by disabling the build providers of the extensions (.resx and .resources) in the web.config.

<buildProviders>
  <remove extension=".resx"/>
  <remove extension=".resources"/>
</buildProviders>

So the calls of HttpContext.GetGlobalResourceObject() and HttpContext.GetLocalResourceObject() will always return null!

If we need to support localization in DNN solutions, we have to use the DNN localization class (DotNetNuke.Services.Localization.Localization) to do so.