Shawn Cicoria - CedarLogic

Perspectives and Observations on Technology

Recent Posts

Sponsors

Tags

General





Community

Email Notifications

Blogs I Read

Archives

Other

Use OpenDNS

Running ASP.NET admin site from command line

Great tip from Dominic Baer.

 

http://leastprivilege.com/2012/06/28/managing-asp-net-membership-and-roles-without-visual-studio/

 

 

 

& ‘C:\Program Files (x86)\Common Files\microsoft shared\DevServer\10.0\WebDev.WebServer40.EXE’ /path:”C:\Windows\
Microsoft.NET\Framework64\v4.0.30319\ASP.NETWebAdminFiles” /port:8080 /vpath:”/asp.netwebadminfiles”

Afterwards you can navigate to the site using this URL:

http://localhost:8080/asp.netwebadminfiles/default.aspx?applicationPhysicalPath=path&applicationUrl=/vdir

Host named site collections and Web Application Extensions

The question came up about whether the mix of an SharePoint 2013 extended web application that may have different authentication providers, could also support Host Named Site Collections (HNSC).

Well, they can.  The documentation isn’t that clear, never seen it explicitly called out.

But, just from a foundation of setting up a web application, extending it, then establishing a HNSC that is addressable from 2 different SP Zones (Extended zone), these are the simple steps:

 

#1
New-SPWebApplication -Name 'Wingtip Public HNSC' -port 8000 -ApplicationPool WingtipHnscAppPool -ApplicationPoolAccount (Get-SPManagedAccount 'wingtip\svc_spcontent') -AuthenticationProvider (New-SPAuthenticationProvider –UseWindowsIntegratedAuthentication)

#1.1 - create a root site collection....
New-SPSite 'http://WINGTIPSERVER:8000' -Name 'Portal' -Description 'Portal on root' -OwnerAlias 'wingtip\administrator' -language 1033 -Template 'STS#0'

#2
Get-SPWebApplication 'http://WINGTIPSERVER:8000' | New-SPWebApplicationExtension -Name "Internet Site" -Zone "Internet" -Port 80 -URL "http://internet.wingtip.com"

#3
New-SPSite 'http://hnsc1.wingtip.com:8000' -HostHeaderWebApplication 'http://WINGTIPSERVER:8000’ -Name 'HNSC1' -Description 'HNSC 1' -OwnerAlias 'wingtip\administrator' -language 1033 -Template 'STS#0'

#4
Set-SPSiteUrl (Get-SPSite 'http://hnsc1.wingtip.com:8000') –Url 'http://p-hnsc1.wingtip.com' –Zone Internet

Building an Office Web Apps (OWA) WOPI Host

UPDATE: January 31, 2014

The solution and project have been updated to MVC5, and Web API 2.  In addition, editing PowerPoint (PPTX), and Excel files has been added.  Word Editing is not part of the solution. Also, PDF viewing is enabled.  See the updated post here:

http://cicoria.com/cs1/blogs/cedarlogic/archive/2014/01/31/wopi-host-sample-has-been-updated.aspx

//end

The latest version of OWA in an on-premises deployment decouples the dependency on SharePoint. For those organizations that perhaps have invested somewhat in non-SharePoint content or document management systems, this offers an opportunity to provide the OWA experience with content from your site.

Get the solution zip

WOPI Host

The WOPI host protocol is defined at this location: http://msdn.microsoft.com/en-us/library/hh643135(v=office.12).aspx

There’s a great overview, introducing WOPI in a blog post from the Office development team here: http://blogs.msdn.com/b/officedevdocs/archive/2013/03/21/introducing-wopi.aspx

In addition, a good overview of the architecture in 2013 (vs. 2010) is shown here:

http://technet.microsoft.com/en-us/library/jj219437.aspx

Callback interface

Note that a WOPI host has to respond to a direct call from OWA for the content. That is illustrated in the above post with this sequence diagram:

image

 

Building WOPI Host

So, for this post, we’re going to cover a working WOPI host that will utilize OWA for display content (Word, Excel, and PowerPoint) with an OWA on-premises deployment.

Primary Interfaces

To get started, the bare minimum implementation, for viewing, requires 2 interfaces implemented as REST endpoints on your WOPI Host.

The solution contains a series of API controllers. The FilesController implements the 2 prmiamry interfaces – the first is a GET which returns the file information; the second returns the content as a stream.

Files

API

Description

GET api/wopi/files/{name}?access_token={access_token}

Required for WOPI interface - on initial view

GET api/wopi/files/{name}/contents?access_token={access_token}

Required for View WOPI interface - returns stream of document.

 

Discovery XML

Within the ~/App_Data location, there’s a discovery.xml file. This is retrieved using the following URL from the OWA server. That XML just needs to be saved to the location.

http://owa1.wingtip.com/hosting/discovery

The solution builds the proper full URL based upon the file type, by examining this file.

Uploading Files / Link Generation

For the sake of testing, you can upload files using the Upload API. This will accept multiple files and return a JSON result that is a collection of Links, with access tokens for each file.

The Link generation is used to generate a fully qualified link that can be used to view an Office file on OWA which will be consumed from the WOPI host.

Access Token

OWA supports the WOPI host use of an access token. Note that the sample provides a HMACSHA256 of the file name using a random generated salt value.

Deployment

Note that the WOPI host MUST be HTTP addressable from the OWA server. In this sample, you also have to turn off HTTPS. Check the OWA TechNet articles on how.

Source Code:

The solution file is here…

Here’s the GetInfo portion

        /// <summary>
        /// Required for WOPI interface - on initial view
        /// </summary>
        /// <param name="name">file name</param>
        /// <param name="access_token">token that WOPI server will know</param>
        /// <returns></returns>
        public CheckFileInfo Get(string name, string access_token)
        {
            Validate(name, access_token);

            var fileInfo = _fileHelper.GetFileInfo(name);
            return fileInfo;
        }

 

And the Contents portion

 /// <summary>
 /// Required for View WOPI interface - returns stream of document.
 /// </summary>
 /// <param name="name">file name</param>
 /// <param name="access_token">token that WOPI server will know</param>
 /// <returns></returns>
 public HttpResponseMessage GetFile(string name, string access_token)
 {
     try
     {
         Validate(name, access_token);
         var file = HostingEnvironment.MapPath("~/App_Data/" + name);
         var rv = new HttpResponseMessage(HttpStatusCode.OK);
         var stream = new FileStream(file, FileMode.Open, FileAccess.Read);

         rv.Content = new StreamContent(stream);
         rv.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
         return rv;

     }
     catch (Exception ex)
     {
         var rv = new HttpResponseMessage(HttpStatusCode.InternalServerError);
         var stream = new MemoryStream(UTF8Encoding.Default.GetBytes(ex.Message ?? ""));
         rv.Content = new StreamContent(stream);
         return rv;
     }
 }

 

And, KeyGen – which generates the hash values

namespace MainWeb.Helpers
{
    public interface IKeyGen
    {
        string GetHash(string value);

        bool Validate(string name, string access_token);
    }
    public class KeyGen : IKeyGen
    {
        byte[] _key;
        int _saltLength = 8;

        static RNGCryptoServiceProvider s_rngCsp = new RNGCryptoServiceProvider();

        public KeyGen()
        {
            var key = WebConfigurationManager.AppSettings["appHmacKey"];
            if (string.IsNullOrEmpty(key))
                throw new ArgumentNullException("must supply a HmacKey - check config");
            _key = Encoding.UTF8.GetBytes(key);
        }

        public string GetHash(string value)
        {
            byte[] salt = new byte[_saltLength];
            s_rngCsp.GetBytes(salt);

            var saltStr = Convert.ToBase64String(salt);
            return GetHash(value, saltStr);
        }

        private string GetHash(string value, string saltStr)
        {
            //Not really secure; must use random salt to ensure non-repeat....
            HMACSHA256 hmac = new HMACSHA256(_key);
            var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(saltStr + value));
            var rv = Convert.ToBase64String(hash);
            return saltStr + rv;
        }


        public bool Validate(string name, string access_token)
        {
            var targetHash = GetHash(name, access_token.Substring(0,_saltLength + 4));  //hack for base64 form
            return String.Equals(access_token, targetHash);
        }


    }
}

 

File Helper

public interface IFileHelper
{
    CheckFileInfo GetFileInfo(string name);
}

public class FileHelper : IFileHelper
{
    public CheckFileInfo GetFileInfo(string name)
    {
        var fileName = GetFileName(name);
        FileInfo info = new FileInfo(fileName);
        string sha256 = "";

        using (FileStream stream = File.OpenRead(fileName))
        using (var sha = SHA256.Create())
        {
            byte[] checksum = sha.ComputeHash(stream);
            sha256 = Convert.ToBase64String(checksum);
        }

        var rv = new CheckFileInfo
        {
            BaseFileName = info.Name,
            OwnerId = "admin",
            Size = info.Length,
            SHA256 = sha256,
            Version = info.LastWriteTimeUtc.ToString("s")
        };

        return rv;
    }


    internal string GetFileName(string name)
    {
        var file = HostingEnvironment.MapPath("~/App_Data/" + name);
        return file;
    }
}

Finally, for this Post a WOPI helper class

public class WopiAppHelper
{
    string _discoveryFile;
    WopiHost.wopidiscovery _wopiDiscovery;

    public WopiAppHelper(string discoveryXml)
    {
        _discoveryFile = discoveryXml;

        using (StreamReader file = new StreamReader(discoveryXml))
        {
            XmlSerializer reader = new XmlSerializer(typeof(WopiHost.wopidiscovery));
            var wopiDiscovery = reader.Deserialize(file) as WopiHost.wopidiscovery;
            _wopiDiscovery = wopiDiscovery;
        }
    }


    public WopiHost.wopidiscoveryNetzoneApp GetZone(string AppName)
    {
        var rv = _wopiDiscovery.netzone.app.Where(c => c.name == AppName).FirstOrDefault();
        return rv;
    }

    public string  GetDocumentLink(string wopiHostandFile)
    {
        var fileName = wopiHostandFile.Substring(wopiHostandFile.LastIndexOf('/') + 1);
        var accessToken = GetToken(fileName);
        var fileExt = fileName.Substring(fileName.LastIndexOf('.') + 1);
        var tt = _wopiDiscovery.netzone.app.AsEnumerable().Where(c => c.action.Where(d => d.ext == fileExt).Count() > 0);

        var appName = tt.FirstOrDefault();

        if (null == appName) throw new ArgumentException("invalid file extension " + fileExt);

        var rv = GetDocumentLink(appName.name, fileExt, wopiHostandFile, accessToken);

        return rv;
    }

    string GetToken(string fileName)
    {
        KeyGen keyGen = new KeyGen();
        var rv = keyGen.GetHash(fileName);

        return HttpUtility.UrlEncode(rv);
    }

    const string s_WopiHostFormat = "{0}?WOPISrc={1}&access_token={2}";
    public string GetDocumentLink(string appName, string fileExtension, string wopiHostAndFile, string accessToken)
    {
        var wopiHostUrlsafe = HttpUtility.UrlEncode(wopiHostAndFile.Replace(" ", "%20"));
        var appStuff = _wopiDiscovery.netzone.app.Where(c => c.name == appName).FirstOrDefault();

        if (null == appStuff)
            throw new ApplicationException("Can't locate App: " + appName);


        var appAction = appStuff.action.Where(c => c.ext == fileExtension).FirstOrDefault();
                    if (null == appAction)
            throw new ApplicationException("Can't locate UrlSrc for : " + appName);

        var endPoint = appAction.urlsrc.IndexOf('?');
        var fullPath = string.Format(s_WopiHostFormat, appAction.urlsrc.Substring(0, endPoint), wopiHostUrlsafe, accessToken); 

        return fullPath;
    }
}
Planning Poker Win8 App.

Check out an in process App for Win 8 that makes use of TFS OData for planning poker

http://www.teamplanningpoker.com/HowTo/Win8/CreateYourFirstPlanningPokerSession

About:

Team Planning Poker was created by Mike Douglas and Deliveron Consulting Services to help distributed and co-located teams effeciently point your projects' backlogs that are using Team Foundation Server or Team Foundation Service.

Deliveron is a technology consulting company focused on delivering end-to-end solutions for clients. We have expertise in custom application development, system integration using BizTalk Server, enterprise portals and workflow with SharePoint, and data management and business intelligence using SQL Server.

Team Foundation Server and Team Foundation Service are part of Visual Studio ALM tools that help improve the effeciency and quality of development. Features includ Project Management, Requirements Management, Development, Test Case Management, Build Automation. There are several ways to start using TFS with little or no additional cost. Sign up with Team Foundation Service to get started.

Office Web Apps–WOPI Host and url paths

If you’re following along with the post on creating a WOPI host, it’s never fully apparent that you MUST adhere to the path shown in the article here:

http://blogs.msdn.com/b/officedevdocs/archive/2013/03/20/introducing-wopi.aspx

That is, the path to your host must contain ‘/wopi/files’.  It wasn’t clear to me in that article, nor could I find it in the WOPI spec. http://msdn.microsoft.com/en-us/library/hh622722(v=office.12).aspx

So, for example, here are some Routes I used in MVC

 

            config.Routes.MapHttpRoute(
                name: "Contents",
                routeTemplate: "api/wopi/files/{name}/contents",
                defaults: new { controller = "files", action = "GetFile" }
                );

            config.Routes.MapHttpRoute(
                name: "FileInfo",
                routeTemplate: "api/wopi/files/{name}",
                defaults: new { controller = "Files", action = "Get" }
                );

 

 

Here are the Actions

public class FilesController : ApiController
    {

        IFileHelper _fileHelper;
        public FilesController() : this(new FileHelper())
        {

        }

        public FilesController(IFileHelper fileHelper)
        {
            _fileHelper = fileHelper;
        }


        /// <summary>
        /// Required for WOPI interface - on initial view
        /// </summary>
        /// <param name="name"></param>
        /// <param name="access_token"></param>
        /// <returns></returns>
        public CheckFileInfo Get(string name, string access_token)
        {
            var fileInfo = _fileHelper.GetFileInfo(name);
            return fileInfo;
        }


        /// <summary>
        /// Required for View WOPI interface - returns stream of document.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="access_token"></param>
        /// <returns></returns>
        public HttpResponseMessage GetFile(string name, string access_token)
        {
            try
            {
                var file = HostingEnvironment.MapPath("~/App_Data/" + name);
                var rv = new HttpResponseMessage(HttpStatusCode.OK);
                var stream = new FileStream(file, FileMode.Open, FileAccess.Read);

                rv.Content = new StreamContent(stream);
                rv.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
                return rv;

            }
            catch (Exception ex)
            {
                var rv = new HttpResponseMessage(HttpStatusCode.InternalServerError);
                var stream = new MemoryStream(UTF8Encoding.Default.GetBytes(ex.Message ?? ""));
                rv.Content = new StreamContent(stream);
                return rv;
            }
        }





    }

 

And here is the helper

 

    public interface IFileHelper
    {
        CheckFileInfo GetFileInfo(string name);
    }

    public class FileHelper : IFileHelper
    {
        public CheckFileInfo GetFileInfo(string name)
        {
            var fileName = GetFileName(name);
            FileInfo info = new FileInfo(fileName);
            string sha256 = "";

            using (FileStream stream = File.OpenRead(fileName))
            using (var sha = SHA256.Create())
            {
                byte[] checksum = sha.ComputeHash(stream);
                sha256 = Convert.ToBase64String(checksum);
            }

            var rv = new CheckFileInfo
            {
                BaseFileName = info.Name,
                OwnerId = "admin",
                Size = (int)info.Length,
                SHA256 = sha256,
                Version = DateTime.Now.ToString("s")
            };

            return rv;
        }


        internal string GetFileName(string name)
        {
            var file = HostingEnvironment.MapPath("~/App_Data/" + name);
            return file;
        }
    }
Note to self–SQL Alias on x64–you need to set both 32/64 cliconfg.exe
1. Start “C:\windows\system32\cliconfg.exe”
2. Start “C:\windows\syswow64\cliconfg.exe”
Office 365 SharePoint and PDF files…

Recently, O365 added some server side rendering of PDF files.  Problem is if you have PDF files and an anonymous site, if a user clicks the PDF link, it will then try to authenticate that users.  Which defeats the purpose of anonymous.

You’ll need to change the links as follows:

Append either to the following (1st opens in the server side PDF reader; 2nd downloads to the client to read.

?Web=1

?Web=0

to the end of the URL.

NuPattern - Home

More from the OuterCurve foundation:

NuPattern is a set of tools and framework that make it easy to create your own branded custom tooling and automation in Visual Studio.

You are now used to seeing different vendors tools and extensions in Visual Studio, some you like, and some offer little value to your project except in the initial phases. But, have you ever considered using and building your own tools, or tools from others developers you follow, ones that create software the way you know you want it created for your projects? Like how your organization or community builds their applications using agreed coding standards, project structures and architectural practices.
NuPattern is the new framework and the tools that enable you to create your own tooling and automation that does exactly that.

Ever tried to create custom tools and templates in Visual Studio? It is impossibly hard, and few can afford to do it, but no longer with
NuPattern. Simply define a model of how you understand the features of your software, and apply templates and automation to it. Then NuPattern will automatically generate a new Visual Studio extension that you can post on a gallery and share with others.

http://nupattern.codeplex.com/

NuPattern - Home

Using S2S for On-Premises Access with Forms Based Access (FBA) in SharePoint 2013

The below diagram represents the flow and interaction when a user, from an external application, makes a OAuth protected call to a SharePoint site. This approach allows for delegated authentication, and since the SharePoint and the external application “can” (they don’t have to) share the Identity Store, we maintain the integrity of the “only 1 identity”.

Note, you can run this without sharing that identity store. In fact, since this is a S2S “High-Trust” approach, all SharePoint cares about is that the external application is “registered” along with a public certificate that will coincide with the signed OAuth token that will appear on the CSOM requests into SharePoint. This certificate, along with the application is registered in SharePoint with the Token Issuer (use the New-SPTrustedSecurityTokenIssuer – and make sure you look up the “-IsTrustBroker” flag).

image 

Spelunking with the SharePoint 2013 App AppContext Tokens and StandardTokens

When writing SharePoint 2013 Provider hosted (autohosted too), SharePoint 2013 Apps provide an App context token, along with other information as part of the initial transition to the provider.  That token, and supporting “tokens” (part of the {standardtoken}) are useful to understand what you have and what you can do wit them.

In the interest of having a simple MVC4 based site, that provides simple Model binding of the SP 2013 App context transition, the sample app in the github repository provides some simple token information, along with a couple CSOM calls back to the SP 2013 environment.

This is purely for getting a better understanding of what’s part of the SP 2013 AppContext, the JwT token, and a simple way to manage this under an MVC4 ASP.NET app.

Here are a couple screen shots:

Source code located here: https://github.com/cicorias/sharepointApps/tree/master/jwtSamples/jwtTokenDump 

Note that there is a hack in there that allows use of HttpSession for keeping the token around.  I didn’t want to much complexity with a cache provider.

SNAGHTML117c637e

SNAGHTML117c9326

SNAGHTML117cbfc1

 

SNAGHTML117ce589 

E-Book Gallery for Microsoft Technologies - TechNet Articles - United States (English) - TechNet Wiki

 

E-Book Gallery for Microsoft Technologies - TechNet Articles - United States (English) - TechNet Wiki

eBook on SignalR…

 

signalR-ebook-done - campusMVP.Net | campusMVP.Net

Visual Studio IE Debug with No Add-ons

 

Sometimes you need IE with no-adds.  For example, colleague of mine was crashing – won’t say which add-on caused it, but easily enough, add a choice to run IE with No-Addons in VS2012.

 

Just add an entry to your Browse with to

ProgramFiles(x86)\Internet Explorer\iexplore.exe

with the  -extoff parameter…

 

clip_image002 

Take a brief look at the Agile management features in TFS 2012

http://msdn.microsoft.com/en-us/magazine/dn189203.aspx

Team Foundation Server

Agile Project Management using TFS 2012

Suliman Battat

Cleaning up IIS Express Sites

Over time, you may end up with lots of sites running in IIS Express.  I like things neat and tidy, and periodically, I’ll run a little cleanup command as follows:

From PowerShell:

$appCmd = "C:\Program Files (x86)\IIS Express\appcmd.exe"

$result = Invoke-Command -Command {& $appCmd 'list' 'sites' '/text:SITE.NAME' }


for ($i=0; $i -lt $result.length; $i++)
{
    Invoke-Command -Command {& $appCmd 'delete' 'site'  $result[$i] }
}
More Posts « Previous page - Next page »