Tag Archives: ASP.NET

Calling CRM from ASP.NET using impersonation to ActOnBehalfOf the logged in user

UPDATE: Please review the comments for this post.  There is a better, easier way to do this using OAuth that works with both the SOAP and REST/OData service.

Sometimes you need to run ASP.NET code outside of Dynamics CRM to achieve your goals.  This usually manifests itself either as a page embedded in CRMs main content area which is accessible via a link in the sitemap similar to the following:

 

image

Another place this is often used is embedding external content through an IFrame in a CRM form.  The general approach is covered in the SDK:

Implement Single Sign-on from an ASPX Webpage or IFRAME

Walkthrough: Single Sign-on from a Custom Web Page

Of course, your code will usually need to call back into Dynamics CRM through the organization (web) service to do things like CRUD on CRM data, etc.  In this scenario, you want CRM to execute code under the context of the logged in user.  The CRM SDK covers how to do this here:

Impersonate Another User

Sample: Impersonate Using the ActOnBehalfOf Privilege

See my CRM Online & Windows Azure Series post for a walkthrough of the Single Sign On (SSO) configuration.  The goal of this post is to bring all of these concepts together in as simple of a “hello world” style code sample as possible.  The sample code is actually the code for the embedded page in the screenshot above (called ActOnBehalfOf.aspx).  The solution is made up of an ASP.NET web form, some code behind the web form, and a helper class I built.  In order to get this code to compile, you are going to have to add the necessary .NET assembly references and fix some of the namespaces.  I’ll leave that exercise to you.

ActOnBehalfOf.aspx:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ActOnBehalfOf.aspx.cs" Inherits="AdfsEnabledReportViewerWebRole.ActOnBehalfOf" %>

 

<html>

    <head runat="server">

        <title></title>

    </head>

    <body>

        <form id="form1" runat="server">

            <div>

                <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False">

                    <Columns>

                        <asp:BoundField DataField="Id" HeaderText="Id" />

                        <asp:BoundField DataField="Name" HeaderText="Name" />

                    </Columns>

                </asp:GridView>

            </div>

        </form>

    </body>

</html>

ActOnBehalfOf.aspx.cs:

 1: using System;

 2: using System.Linq;

 3: using Microsoft.Xrm.Client;

 4: using Microsoft.Xrm.Sdk.Client;

 5:  

 6: namespace AdfsEnabledReportViewerWebRole

 7: {

 8:     public partial class ActOnBehalfOf : System.Web.UI.Page

 9:     {

 10:         protected void Page_Load(object sender, EventArgs e)

 11:         {

 12:             var contextConnection = ActOnBehalfOfHelper.CreateContextAndConnection();

 13:             CrmConnection conn = contextConnection.Connection;

 14:             OrganizationServiceContext ctx = contextConnection.Context;

 15:  

 16:             // CallierId is what forces CRM to execute the API calls within the security context of the CRM User

 17:             conn.CallerId = ActOnBehalfOfHelper.GetCallerId();

 18:  

 19:             var accountQuery = from a in ctx.CreateQuery<Account>()

 20:                         select new Account

 21:                         {

 22:                             Id = a.Id,

 23:                             Name = a.Name

 24:                         };

 25:  

 26:             var accounts = accountQuery.ToList();

 27:  

 28:             GridView1.DataSource = accounts;

 29:             GridView1.DataBind();

 30:         }

 31:     }

 32: }

If you’ve reviewed the resources in this post, then ActOnBehalfOf.aspx and ActOnBehalfOf.aspx.cs should be pretty self explanatory.  It’s a page with a GridView.  The code behind queries CRM for data using the organization service.  Note that Account from line 19 comes from a class file where I used crmsvcutil.exe to generate the class.  I always use Erik Pool’s approach to only generate classes I need in my code.  I digress.  The code sets the CallerId property of the CrmConnection object instance before executing the code.  By doing this, CRM will execute all calls made to through the OrganizationServiceContext instance as the CRM user based on the CallerId value passed in.  CallerId is the GUID of the CRM user who needs to be impersonated.  The ActOnBehalfOfHelper does the real work to get the proper GUID based on the claims available to the ASP.NET page.  Specifically, it uses the UPN claim value to find the CRM user.  Once the CRM user is found, the code returns the Id of the CRM user as a GUID. 

ActOnBehalfOfHelper.cs:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using Microsoft.IdentityModel.Claims;

using Microsoft.Xrm.Client;

using Microsoft.Xrm.Client.Services;

using Microsoft.Xrm.Sdk.Client;

 

namespace AdfsEnabledReportViewerWebRole

{

    public static class ActOnBehalfOfHelper

    {

 

// ReSharper disable InconsistentNaming

        private const string CRM_CALLERID = "CRM_CALLERID";

// ReSharper restore InconsistentNaming

 

        public static ContextConnection CreateContextAndConnection()

        {

            var contextConnection = new ContextConnection();

            // Connect to CRM with a single named user (i.e. system account / trusted subsystem model) who has the ActOnBehalfOf privelege

            contextConnection.Connection =

                CrmConnection.Parse("Url=[YOUR_ORG_URL];Username=[YOUR_USERNAME];Password=[YOUR_PASSWORD]");

            contextConnection.Context =

                new OrganizationServiceContext(new OrganizationService(contextConnection.Connection));

 

            return contextConnection;

        }

 

        public static Guid GetCallerId()

        {

            Guid callerId;

            var contextConnection = CreateContextAndConnection();

            var ctx = contextConnection.Context;

 

            // NOTE: I am caching the CallerId to minimize calls to the CRM Organization Service.

            // For production code, you should not store the CallerId in plain text in a cookie.

            // Malicious code, once authenticated, can change the cookie value and execute as another caller.

            // You could apply encryption when creating the cookie and decryption when reading 

            // the cookie value:

            // http://msdn.microsoft.com/en-us/library/windowsazure/hh697511.aspx

            // You could even encrypt/decrypt the cookie name to obfuscate the purpose of the cookie.

            // Alternatively, find a different approach to cache the CallerId value (ASP.NET Session for example)

            // or simply don't cache the CallerId.

 

            HttpCookie callerIdCookie = HttpContext.Current.Request.Cookies[CRM_CALLERID];

 

            // If the cookie exists, reuse the Guid string value to execute the call as the current user

            // If not, then query CRM to get the Guid of the authenticated user based on the upn claim

            if (callerIdCookie == null)

            {

                ClaimCollection claims = ((IClaimsIdentity) HttpContext.Current.User.Identity).Claims;

 

                IEnumerable<Claim> claimQuery = from c in claims

                                                where

                                                    c.ClaimType ==

                                                    "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"

                                                select c;

 

                Claim upnClaim = claimQuery.FirstOrDefault();

 

                var userQuery = from user in ctx.CreateQuery<SystemUser>()

                                where user.DomainName == upnClaim.Value

                                select user.SystemUserId.Value;

 

                callerId = userQuery.FirstOrDefault();

                if (callerId == Guid.Empty)

                {

                    // Send HTTP status code of 403

                    // See http://en.wikipedia.org/wiki/List_of_HTTP_status_codes

                    HttpContext.Current.Response.StatusCode = 403;

                    HttpContext.Current.Response.End();

                }

 

                string callerIdString = callerId.ToString();

                HttpContext.Current.Response.Cookies.Add(new HttpCookie(CRM_CALLERID, callerIdString));

            }

            else

            {

                callerId = new Guid(callerIdCookie.Value);

            }

 

            return callerId;

        }

    }

 

    public class ContextConnection

    {

        public CrmConnection Connection { get; set; }

 

        public OrganizationServiceContext Context { get; set; }

    }

}

Note the comments in the code.  I am doing some caching of the user GUID in a cookie.  Right now, the cookie and cookie value is in plain text.  As I state, this is done for simplicity of the sample.  Make sure you read the comments and make the proper adjustments to protect access to the CallerID GUID from malicious code/callers.

@devkeydet

Eliminating manual device registration

If you are connecting to CRM Online with a Live ID, you need to register a Device ID and Device Password when using the organization service from an outside caller.  I walk through this in my Connecting to CRM Online from an outside caller post.  Thanks to some internal discussions with folks on the SDK team, I discovered a simpler way that doesn’t require manually registering and putting the DeviceId /DevicePassword in the config file.  First you’ll have to download and use the Helper Code: DeviceIdManager Class from the SDK.  In addition to the references from the previous post, you’ll need to add references to System.Security.dll and System.ServiceModel.dll to get it to build.  Once you have done that, you can eliminate the DeviceId and DevicePassword settings from the connection string:

<connectionStrings>

    <add name="CrmOnline" connectionString="Url=[YOUR_CRM_ORG_URL]; Username=[YOUR_LIVE_ID]; Password=[YOUR_PASSWORD];"/>

    </connectionStrings>

You can now connect to CRM Online like so:

var connection = new CrmConnection("CrmOnline");

connection.DeviceCredentials = DeviceIdManager.LoadOrRegisterDevice();

var context = new XrmServiceContext(connection);

DeviceIdManager.LoadOrRegisterDevice() automates the manual steps from the Connecting to CRM Online from an outside caller post.  I’m pretty sure it requires full trust, but haven’t taken the time to confirm.  If you have, leave me a comment to confirm/correct.

@devkeydet

Connecting to CRM Online from an outside caller

UPDATE: Thanks to some internal discussions with folks on the SDK team, I discovered a simpler way that doesn’t require manually putting the deviceid/devicepassword in the config file.  I decided to leave this post as-is for two reasons.  First, because the easier way requires full trust and won’t work for you if your hoster or IT department requires partial trust.  Second, so people understand what the easier way automates.  I’ve updated this post with a “you can skip this step” explanation. 

Scenario:

“I want to make calls to CRM from an ASP.NET page, .NET middle tier code, etc.  How do I do it with CRM Online?”

Simple answer right?  Maybe for the persistent developer who is happy to reverse engineer the steps from the SDK documentation.  However, I have to say the SDK doesn’t spell it out for you step by step. 

The following resources seem to help on the surface:

Walkthrough: Build a Web Application That Connects to Microsoft Dynamics CRM 2011 Using Developer Extensions

ASP.NET Web Forms and Data Binding

Create Early Bound Entity Classes with the Code Generation Tool (CrmSvcUtil.exe)

Simplified Connection to Microsoft Dynamics CRM

Sample: Authenticate Users with Microsoft Dynamics CRM Web Services

But there’s a little CRM Online nuance that sort of gets lost in the shuffle unless you connect the dots across some of these articles.  CRM Online, when using a Live ID, requires that you use a DeviceId and DevicePassword when connecting.  While the Simplified Connection to Microsoft Dynamics CRM article does explain this, it leaves it up to the reader to hunt down the whole Device ID setup process.  This post is my attempt to connect the dots in a step by step walkthrough of building a simple ASP.NET Web Forms page that connects to CRM Online using a Live ID.

Let’s start by creating a new ASP.NET Empty Web Application and call it CrmOnlineConnectedWebApp:

image

Add the following references from the SDKbin folder.

  • Microsoft.Xrm.Client.dll
  • Microsoft.Xrm.Sdk.dll

Add the following references from .NET.

  • System.Data.Services.dll
  • System.Data.Services.Client.dll
  • System.Runtime.Serialization.dll

DEVICE REGISTRATION

You can skip this next step and jump to CREATE EARLY BOUND TYPES if you follow the enhancement in the Eliminating manual device registration post.

There’s a section titled “To Generate your individual device ID and password” in the Create Early Bound Entity Classes with the Code Generation Tool (CrmSvcUtil.exe), but it’s easy to miss.  Follow the instructions from that section:

  • Open and build the DeviceRegistration project: SDKToolsDeviceRegistrationDeviceRegistration.csproj.
  • Run the executable file from the command line. To register your device, set the /operation parameter to Register.
  • C:deviceregistration.exe /operation:Register
  • Copy the displayed device ID and password values and use them as the deviceid and devicepassword parameter values when you run the CrmSvcUtil tool.

The easiest way to copy the Device ID and the Device Password from the console is to right-click and select all:

image

…right-click again (this will copy the contents) and paste into notepad.  Then you can copy the actual values. 

CREATE EARLY BOUND TYPES

Let’s create some early bound types as described in step one of Walkthrough: Build a Web Application That Connects to Microsoft Dynamics CRM 2011 Using Developer Extensions.  However, we need to remove the domain flag:

CrmSvcUtil.exe /codeCustomization:"Microsoft.Xrm.Client.CodeGeneration.CodeCustomization, Microsoft.Xrm.Client.CodeGeneration" /out:Xrm.cs /url:[YOUR_CRM_ROOT]/XRMServices/2011/Organization.svc /username:[YOUR_USERNAME] /password:[YOUR_PASSWORD] /namespace:Xrm /serviceContextName:XrmServiceContext

Now add the generated file to your Visual Studio project.  Now is a good time to build.  If the build fails, you are likely missing an assembly reference.  Now let’s add a Web Form called Default.aspx:

image

Add a GridView to the markup of Default.aspx:

image

The next steps are slightly different if you decided to use the approach in my Eliminating manual device registration post.  Switch to Default.aspx.cs and paste the following code into the Page_Load handler:

var connection = new CrmConnection("CrmOnline");

var context = new XrmServiceContext(connection);

 

var query = from a in context.AccountSet

            select new

            {

                a.Name,

                a.AccountNumber,

                a.Address1_City

            };

 

GridView1.DataSource = query.ToList();

GridView1.DataBind();

The final step is to put the CrmOnline connection string into your web.config:

<connectionStrings>

    <add name="CrmOnline" connectionString="Url=[YOUR_CRM_ORG_URL]; Username=[YOUR_LIVE_ID]; Password=[YOUR_PASSWORD]; DeviceId=[DEVICE_ID_FROM_CONSOLE]; DevicePassword=[DEVICE_PWD_FROM_CONSOLE]"/>

    </connectionStrings>

You should be able to run the app now and see data in the GridView.

@devkeydet

Help make Microsoft developer technologies better!

Follow devkeydet on Twitter

Ron Jacobs just blogged about how .NET developers can provide feature feedback and vote on WCF/WF features.

http://blogs.msdn.com/b/rjacobs/archive/2011/04/14/how-you-can-make-wf-wcf-better.aspx

Many Microsoft product teams are doing this nowadays. It still surprises me how many .NET developers don’t realize these feature voting sites exist. In addition to WF/WCF, I am aware of these:

http://wpdev.uservoice.com/forums/110705-app-platform

https://windowsphone7community.uservoice.com/forums/84435-feature-feedback

http://data.uservoice.com/forums/72027-wcf-data-services-feature-suggestions

http://data.uservoice.com/forums/72025-ado-net-entity-framework-ef-feature-suggestions

http://dotnet.uservoice.com/forums/40583-wpf-feature-suggestions

http://dotnet.uservoice.com/forums/4325-silverlight-feature-suggestions

http://dotnet.uservoice.com/forums/87171-visual-basic-content-requests

http://dotnet.uservoice.com/forums/57026-wcf-ria-services

http://www.mygreatwindowsazureidea.com/pages/34192-windows-azure-feature-voting

http://www.mygreatwindowsazureidea.com/forums/35889-microsoft-codename-dallas-feature-voting

http://www.mygreatwindowsazureidea.com/forums/44459-sql-azure-data-sync-feature-voting

http://www.mygreatwindowsazureidea.com/forums/34685-sql-azure-feature-voting

http://www.mygreatwindowsazureidea.com/forums/100417-sql-azure-reporting-feature-voting

http://www.mygreatwindowsazureidea.com/forums/40626-windows-azure-appfabric-feature-voting

http://www.mygreatwindowsazureidea.com/forums/103009-windows-azure-code-samples-voting

http://www.mygreatwindowsazureidea.com/forums/103403-windows-azure-content-voting

http://aspnet.uservoice.com/forums/41199-general

http://aspnet.uservoice.com/forums/41201-asp-net-mvc

http://aspnet.uservoice.com/forums/41202-asp-net-webforms

http://aspnet.uservoice.com/forums/50615-orchard

http://aspnet.uservoice.com/forums/100405-performance

http://aspnet.uservoice.com/forums/41233-visual-studio-performance-feedback

Let me know in the comments if I’ve missed any.  I’ll add them.

The unofficial biography of Microsoft, ASP.NET, Ajax, and jQuery

The Microsoft Ajax strategy has been a bit of a winding road.  Even if you’ve kept up with the evolution, as I have, you might be a little confused.  Today, Scott Guthrie published a blog post announcing that Microsoft’s first submissions to jQuery are official.  There was a corresponding blog post on the jQuery blog.  There’s also an overview video about the Microsoft submissions by Stephen Walther over on Ch.9.  What’s not covered in these posts is the historical evolution that brings us to what was announced today.  You may may be saying to yourself “So what?”  Well, the internet is full of blog posts on the topic.  Depending on the place in time when the blog post or article you come across via your favorite search engine (Bing of course) was published, you might be led astray.  Knowing what’s what and the history helps you from making the mistake of following stale information.  Dave Ward to the rescue!  He wrote up what I think if as the “unofficial biography” on the topic.  Definitely worth a read:

Javascript Libraries and ASP.NET: A Guide to jQuery, AJAX and Microsoft

SCREENCAST: Running Reporting Services Reports in Windows Azure


digg_url = “http://blogs.msdn.com/devkeydet/archive/2010/02/24/screencast-running-reporting-services-reports-in-windows-azure.aspx&#8221;;digg_title = “SCREENCAST: Running Reporting Services Reports in Windows Azure”;digg_bgcolor = “#555555”;digg_skin = “normal”;

http://digg.com/tools/diggthis.js

digg_url = undefined;digg_title = undefined;digg_bgcolor = undefined;digg_skin = undefined;


In this screencast, I show you how to run a SQL Server Reporting Services 2008 report in Windows Azure using the ReportViewer control that ships with Visual Studio 2010.  As an added bonus, I demonstrate using ReportViewer against an OData service through the use of WCF Data Services client libraries and the ObjectDataSource.


Get Microsoft Silverlight


Direct link to Ch. 9 post:


https://channel9.msdn.com/posts/keydet/Running-Reporting-Services-Reports-in-Windows-Azure/

I totally missed this… Web App Toolkits

digg_url = “http://blogs.msdn.com/devkeydet/archive/2010/02/23/i-totally-missed-this-web-app-toolkits.aspx&#8221;;digg_title = “I totally missed this… Web App Toolkits”;digg_bgcolor = “#555555”;digg_skin = “normal”;http://digg.com/tools/diggthis.jsdigg_url = undefined;digg_title = undefined;digg_bgcolor = undefined;digg_skin = undefined;

http://www.microsoft.com/web/downloads/webapptoolkits/

“These FREE Web App Toolkits help you complete common web development tasks and quickly add new features to your apps. Whether it’s Bing Maps integration or adding social capabilities to your site, there’s a toolkit for you. Download and install them today.”

http://blogs.msdn.com/webapptoolkits/

My friend and coworker Vlad made me aware of these.  I’ve been living in the dark.  Apparently, they have been around since September.  I didn’t even know they exist until last week. 

Here’s a dump of all the toolkits available as of today:

Web App Toolkit for "Freemium" Applications
Web App Toolkit for Calendars
Web App Toolkit for Bing Maps
Web App Toolkit for IE8
Web App Toolkit for Bing Search
Web App Toolkit for REST Services
Web App Toolkit for Mobile Web Applications
Web App Toolkit for Template-Driven Email
Web App Toolkit for Making Your Website Social
Web App Toolkit for FAQs

You will find screencasts for most of the toolkits at https://channel9.msdn.com/tags/web+application+toolkit/.  I just checked out the IE8 toolkit screencast.  You basically get ASP.NET controls that simplify the building of Web Slices, Accelerators, and Visual Search Providers for IE8.  You also get a sample site that incorporates the controls. 

I will be watching the rest of the screencasts over the next week or so.  Good Stuff!