Archive
3 Things Azure ACS has done for me lately…
In my pet project Crossfit Benchmarks – I have three very specific security requirements and Azure ACS (Access Control Services) has allowed me to implement all three.
Here is the list of requirements:
- Users must login to access secure sections of the web application AND my application will not do any of the following
- provide authentication & authorization services
- store & manage user credentials
- Data stored in my application must be partitioned by individual users – i.e. your data is yours, mine is mine, ours data does not inter-mingle
- The Restful web services that serve the data to my applications must be secured AND my services will not do any of the following
- provide authentication & authorization services
- store & manage client application credentials / keys / or shared secrets
Yeah – I know it sounds too good to be true right? But it was pretty easy to do all three.
User Authentication & Authorization
I was able to login to my Azure portal, navigate to the Access Control section, and simply create a relying party application that relies on various third party identity providers (Google, Facebook, Yahoo, and Windows Live). In this particular scenario – the relying party application is my ASP.NET MVC Web Application. After just a few clicks I had enabled authentication & authorization for Facebook, Google, Yahoo, and Windows Live. Each of these identity providers manage credentials, provide authentication – NOT my web applicaiton.
So the moral of the story here is that – for my web application I don’t care about authenticating and authorizing users. As far as my web application is concerned – if Azure ACS says that the 3rd party Identity Providers have authenticated & authorized the person trying to access the secured sections of my web application then that is secure enough for me.
If you wanted to – you could even integrate your on-premise Active Directory with cloud based azure ACS – but I have never had to do this and frankly I think this scares the daylights out of many IT Organizations… can you say “private cloud” – oh wait… that’s a topic for another day.
Partitioning Data by Individual Users
The beauty of using the Identity Providers in Azure ACS is that they all give SAML Tokens back to my application. SAML has been around since ~2005 and it has typically been complicated to implement. This article won’t get too much into SAML or Tokens, but tokens are basically tidbits of user information that are handed out by identity providers. Tokens ARE NOT credentials – they are a set of “claims” that can be made about a security context, i.e. “name identifier” + “identity provider” can be used to uniquely identify a user accessing my system. I don’t know “who” this user is, just that their token is valid, the tokens came from a reputable source, and now I can partition data using these tokens handed to my application.
So how does all this magic work? It’s pretty cool… and here is the short version of my understanding of how it works…
- User tries to access a secure section of my app
- My app redirects user to Azure ACS because my app is configured to expect Azure ACS tokens
- User logs into their identity provider and grants my app access
- Identity provider issues tokens and redirects user back to Azure ACS
- Azure ACS forwards user to where they wanted to go in the first place (my application)
- My application inspects the tokens to make sure they are valid
- If the tokens are valid – we will either create new data for the user OR load their existing data
So what happens if Facebook, Google, Yahoo, or Windows Live change the way that they issue name identifiers? I don’t know – I suppose that if they change the name identifier issued to my credentials – then all of the data on the internet relying on this name identifier value would be unreachable… so let’s hope they don’t do that…
So why is this cool again?
- I don’t have to store credentials for my users – I am in the business of storing data for my application, not your credentials
- I think it’s a major PITA for users to have to create account after account after account, etc, just to use a simple web app
- Each time a new application rolls out a new authentication & authorization scheme – the internet actually becomes less secure, because if the user uses the same username / password as they have in other systems, they have just duplicated that data now there is another means for hackers to get their data – in other words, the more times you share a secret – the less secure it actually is – at some point, someone will tell the secret again (i.e. get hacked)…
Securing Restful Services
In my pet project, I am planning on having an ASP.NET Web Client, Android Client, Windows 8 Client, and maybe an iOS Client. Each of these clients will use basic http to talk to my services, and I want to make sure that only clients that are authenticated and authorized can access my services API. In previous lives I have built this by hand using WCF and SQL Server, plus API Keys and HMAC validation. I really didn’t want to go there again.
So it turns out that Azure ACS has the concept of Service Identities. Basically, I can assign a set of shared secrets for each client. My client can then use the shared secret to request a security token from Azure ACS. Azure ACS will verify the shared secret and return a security token that can then be forwarded to my web service api.
Is this 100% bulletproof? No, people can crack my apps in the app store and figure out the shared secret, and build their own apps with my shared secret. This is not the end of the world – I am not dealing with financial or medical data – so if a hacker is really that bored – then go nuts. If I catch it, I can simply change the shared secret in Azure and push updated builds to the various app stores.
So – for now, it is secure enough. Once I actually start building the Android, Windows 8, and iOS versions I may revisit this. Right now, I am working on the ASP.NET MVC flavor, so they would actually have to hack my Azure Role to get access to the shared secret.
In Summary
Compared to other things I have seen in my previous years of building software and implementing security solutions – using Azure ACS has been pretty easy, it is pretty cheap – especially considering the time it saves and security it provides. In future applications, if appropriate, I will continue to look for ways to use Azure ACS.
Detecting the File Download Dialog in the Browser
This post by Jesse Taber is amazing. Totally saved me a bunch of headache and internal debate.
Do a normal form post that returns a file stream result and then detect when the application is finished returning the file.
Amazing Jesse, Thank You…
Moral of the story is this…
- do a normal form post to kick off building or fetching a file stream
- post a token in the form
- show a waiting indicator
- start a timer, look for a cookie containing the token value
- when / if the cookie exists and the value matches, hide the waiting indicator
In my implementation when I do the post, I am reading from a database, building a power point presentation in memory, saving the memory stream into blob storage, and then streaming the file down to the browser.
Before & After…
With Jesse’s help, I didn’t need to do any iFrame magic or ajax tricks.
It works in FF v10.0, IE v9.0, Chrome v17.0
Other Resources:
Recap on Expired Sessions and Idle Users in ASP.NET MVC
Just a quick recap of the user stories that I recently had to deal with regarding expired sessions and idle users in an ASP.NET MVC web application that I am working on. In addition, this post outlines the high level solution and includes track backs to the articles that show the implementation details of my solution.
User Stories
#1 – As a user of the application, sometimes I get distracted and I have to take my attention away from the application. Sometimes when I try to navigate to a page after a period of inactivity I encounter an error in the application regarding my session and I am unable to get to the page that I need to get to. Please fix this issue so that I can do the task at hand.
#2 – As a user of the application, sometimes I get distracted and I have to take my attention away from the application. Sometimes when I click a button on the page to update some data, I get an error message talking about an invalid session. When I click the button the page tells me that it is trying to save my data, but the page never comes back with the success message, just the error message. Please fix this issue so that I can submit my changes.
#3 – As a user of the application, sometimes I get distracted and I have to take my attention away from the application. New features have been recently introduced such that I am automatically re-directed to the login page. Instead of redirecting me, can you simply check to see if I am still at the computer looking at the screen before you redirect me? Other applications, like my bank’s web site ask me if I want to continue or log me out if I am away, why can’t we do this?
Technical Notes
- session can expire for various reasons. it can expire if the IIS application pool recycles, it can expire due to the user not making requests back to the server, and probably a few other reasons…
- if we make a request to a page that requires session state, the request will probably result in an error
- if we make an AJAX request to a page that requires session state, the request will probably result in an error
- even if we can detect that the request is an AJAX request, we will probably have to work some magic such that the response returned to the AJAX call can be used as an indicator that the session has expired, and we need to do something special, like redirect to a login page
- tracking user activity in a web client sounds scary and complicated. maybe the user is really at the computer, and maybe they really are interacting with the user interface, but maybe they are typing a really long letter, but whatever they are doing, they aren’t communicating back to the server, so their session can expire on the server
My Solution
- use a custom action filter to look for calls made to MVC controllers. this action filter should know if the request is a normal request or an AJAX request and return the proper response, in the event that the session has expired. This ActionFilter will prevent any session dependent code from executing when the session has expired, and thus minimize runtime errors. Ultimately, this ActionFilter should help us ensure that the user always has a valid session when needed.
- use a jQuery plugin to check for user activity and occasionally poll the server to keep their session alive. if the user appears to be inactive, ask the user if they want to continue. if the user fails to take action, logout the user. if the user does take an action, keep their session alive and let them continue working.
Dealing with Idle Clients and Expiring Sessions in ASP.NET MVC
In my previous article I demonstrate how I have used a custom ActionFilter to handle expired sessions when making AJAX calls to controllers and also when making normal calls to a controller in ASP.NET MVC.
In this post, I discuss my approach regarding detecting idle clients for specific pages and keeping the users session alive while they are still active (but not necessarily interacting directly with the server).
I decided to use the jQuery Idle Timeout Plugin from Eric Hynds. This plugin basically detects when a user has become idle, and prompts the user to take an action. If no action is taken, the user can be booted to some other location. Another cool thing about this plugin is that it can poll the server to keep the users session alive. This is an important feature because if they are doing a bunch of things client side, and not really communicating back with the server, their session can expire. So, if they are active, this plugin keeps their session alive.
Expected Behavior
I have a data entry screen, that depends on session, and if the user is idle for too long, I want to give them the option to keep working (thus keeping their session alive). On the other hand, if they really aren’t there, I want to redirect them to the logon page. The image below shows how I decide to prompt the user. This could just as easily be a jQuery Dialog or something else.
Implementation
In my implementation, I only want to enable the idle timeout on certain pages. All of my pages use Site.Master. My Site.Master contains a reference to a .js file that deals with session expiration, so this is a good spot for my method to deal with idle timeouts. Each page tends to have its own .js file, so for each page that requires activity monitoring, I write a method to start the tracking. There may be a more elegant solution, but this is where I have landed.
So here are the steps that I took to get the functionality that I wanted:
- include the idle timeout plugin scripts into my master page
- add the idle timeout UI elements to my master page
- add a method to my site’s .js file that, when invoked, will start tracking activity
- add a method to my page’s .js file that, when invoked, will call to the method in the site’s .js file
- add an action method to my HomeController to deal with returning the expected Keep Alive content
Add plugin scripts into my master page
<head runat="server">
<title>
<asp:ContentPlaceHolder ID="TitleContent" runat="server" />
</title>
<link href="<%= Url.Content("~/Content/layout.css") %>" rel="stylesheet" type="text/css" />
<link href="<%= Url.Content("~/Content/color.css") %>" rel="stylesheet" type="text/css" />
<link href="<%= Url.Content("~/Content/type.css") %>" rel="stylesheet" type="text/css" />
<script src="<%= Url.Content("~/Scripts/jquery-1.4.1.min.js")%>" type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/jquery.idletimeout.js")%>" type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/jquery.idletimer.js")%>" type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/Shared/ajaxSessionExpiration.js")%>" type="text/javascript"></script>
<asp:ContentPlaceHolder ID="HeadContent" runat="server" />
</head>
Add idle timeout UI elements to my master page
<body>
<div class="page">
<div id="idletimeout">
You will be logged off in
<span><!-- countdown place holder --></span>
seconds due to inactivity.
<a id="idletimeout-resume" href="#">
Click hereto continue using this web page</a>.
</div>
<!-- remaining code elided -->
</div>
</body>
Add the method to the site’s .js file to enable activity tracking
function EnableTimeout() {
$(function () {
$.idleTimeout('#idletimeout', '#idletimeout a', {
idleAfter: 600, // 10 minutes
pollingInterval: 300, // 5 minutes
keepAliveURL: '/Home/KeepAlive',
serverResponseEquals: 'OK',
warningLength: 60, // give the user 60 seconds to respond
onTimeout: function () {
// redirect to login if the user takes no action
$(this).slideUp();
window.location = "/Account/Logon";
},
onIdle: function () {
$(this).slideDown(); // show the warning bar
},
onCountdown: function (counter) {
$(this).find("span").html(counter); // update the counter
},
onResume: function () {
$(this).slideUp(); // hide the warning bar
},
onAbort: function () {
window.location = "/Account/Logon";
}
});
});
};
Add method to my page’s .js file to start activity tracking
$(document).ready(function () {
var cloudId = $("#cloudId").attr("value");
$("#createBookmark_cloudId").attr("value", cloudId);
$("#createBookmark_bookmarkId").attr("value", -1);
// start tracking actovity
EnableTimeout();
});
Add an action method to my HomeController to deal with the KeepAlive
[HttpGet]
public ActionResult KeepAlive()
{
return new ContentResult { Content = "OK", ContentType = "text/plain" };
}
Using an ActionFilter to Redirect to Login when Session Expires (Part2)
In my previous post, I describe how I used a custom MVC ActionFilter to detect if a user’s session had expired and redirect the user to the site’s login page. In this post, I describe how I modify the ActionFilter to account for AJAX requests.
In this scenario, I am assuming that:
- the user has taken some action on the screen that causes an AJAX call to be made
- the user’s session has expired
- there is no client side session / activity detection in place (yet)
- In part 1, the action filter simply redirects the user to the login page. This works great, providing that the call is not an AJAX call. If the AJAX call is expecting a JSON response, the ActionFilter written in part one cause the AJAX call to fail because, the ActionFilter will detect that the session has expired and return the login page to the AJAX call.
- Similarly, if the AJAX call is expecting a partial page update / html response to be returned, the ActionFilter will return the login page to the AJAX call, and the login page will be injected into the page where the partial page update would normally occur.
- In my implementation, I borrowed the concept from Aaron H. at Pearl Technology, however, I took his approach a step further. In Aaron’s approach, he is relying on the AJAX callback to return some html content (e.g. the login page), and he looks for a special key in the html content.
- My implementation is to have my ActionFilter return a “special” json result if the call is an AJAX call. I decided that I didn’t want to load the entire login page and it’s content, just to look for a special hidden field in the login page, and that it would probably be easier to send back an explicit JSON object that is a hook for my javaScript to redirect.
Here is the new test case that illustrates how I have extended my ActionFilter…
[Test]
public void ActionFilter_Returns_JsonResult_WhenCall_IsAjaxRequest()
{
request.BackToRecord(BackToRecordOptions.All);
request.Replay();
request.Stub(it => it["X-Requested-With"]).Return("XMLHttpRequest");
request.Stub(it => it.Headers).Return(headers);
filter.OnActionExecuting(filterContext);
var data = filterContext.Result.AssertResultIs<JsonResult>().Data;
Assert.AreEqual("{ LogonRequired = True }", data.ToString());
}
Here is the updated action filter (the previous version of the action filter can be found in my previous post)…
namespace TagCloud.Web.Framework.CustomActionFilters
{
public class SessionExpireActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var context = filterContext.HttpContext;
if (context.Session != null) {
if (context.Session.IsNewSession) {
string sessionCookie = context.Request.Headers["Cookie"];
if ((sessionCookie != null) && (sessionCookie.IndexOf("ASP.NET_SessionId") >= 0)) {
ActionResult result = null;
if (context.Request.IsAjaxRequest()) {
result = new JsonResult { Data = new { LogonRequired = true } };
}
else {
string redirectTo = "~/Account/Logon";
if (!string.IsNullOrEmpty(context.Request.RawUrl)) {
redirectTo = string.Format("~/Account/Logon?ReturnUrl={0}",
HttpUtility.UrlEncode(context.Request.RawUrl));
}
result = new RedirectResult(redirectTo);
}
filterContext.Result = result;
}
}
}
base.OnActionExecuting(filterContext);
}
}
}
My javaScript code is slightly different than Aaron’s in that I try to parse a JSON string and I look for a specific value in the JSON object. If that JSON doesn’t exist, then I simply exit the javaScript and no redirects are performed. Here is the javaScript code…
///
Sys.Net.WebRequestManager.add_completedRequest(function (result) {
IsLogonRequired(result.get_responseData());
});
$("*").ajaxComplete(function(e, xhr, settings){
IsLogonRequired(xhr.responseText);
});
function IsLogonRequired(response_data) {
var data = null;
try {
var data = $.parseJSON(response_data);
}
catch (ex) {
//content is not json so we dont care that jsonParse failed
};
if (data != null && data.LogonRequired != undefined && data.LogonRequired == true)
window.location.href = "/Account/Logon";
};
Just for reference, here is the full blown test fixture for the custom ActionAttribute…
using System;
using NUnit.Framework;
using MvcContrib.TestHelper;
using System.Web.Mvc;
using TagCloud.Web.Framework.CustomActionFilters;
using Rhino.Mocks;
using System.Web;
using System.Collections.Specialized;
namespace TagCloud.Web.Framework.Test.CustomActionFilters
{
[TestFixture]
public class SessionExpireActionFilterAttributeTest
{
[Test]
public void ActionFilter_Returns_JsonResult_WhenCall_IsAjaxRequest()
{
request.BackToRecord(BackToRecordOptions.All);
request.Replay();
request.Stub(it => it["X-Requested-With"]).Return("XMLHttpRequest");
request.Stub(it => it.Headers).Return(headers);
filter.OnActionExecuting(filterContext);
var data = filterContext.Result.AssertResultIs().Data;
Assert.AreEqual("{ LogonRequired = True }", data.ToString());
}
[TestCase((string)null, "~/Account/Logon")]
[TestCase("/SomeController/SomeAction", "~/Account/Logon?ReturnUrl=%2fSomeController%2fSomeAction")]
[TestCase("/aaa/bbb", "~/Account/Logon?ReturnUrl=%2faaa%2fbbb")]
public void ActionFilter_Redirects_WhenSessionExpires(string rawUrl, string expected)
{
request.Stub(it => it.RawUrl).Return(rawUrl);
filter.OnActionExecuting(filterContext);
filterContext.Result.AssertHttpRedirect().ToUrl(expected);
}
[SetUp]
public void Setup()
{
filter = new SessionExpireActionFilterAttribute();
filterContext = MockRepository.GeneratePartialMock();
contextBase = MockRepository.GenerateStub();
session = MockRepository.GenerateStub();
request = MockRepository.GenerateStub();
headers = new NameValueCollection();
headers["Cookie"] = "ASP.NET_SessionId";
contextBase.Stub(it => it.Session).Return(session);
contextBase.Stub(it => it.Request).Return(request);
request.Stub(it => it["X-Requested-With"]).Return("");
request.Stub(it => it.Headers).Return(headers);
session.Stub(it => it.IsNewSession).Return(true);
filterContext.HttpContext = contextBase;
}
private HttpContextBase contextBase;
private SessionExpireActionFilterAttribute filter;
private NameValueCollection headers;
private HttpRequestBase request;
private HttpSessionStateBase session;
private ActionExecutingContext filterContext;
}
}
Using an ActionFilter to Redirect to Login when Session Expires (Part 1)
Note -> the code in this post has evolved please see part 2
Recently I have been working with ASP.NET MVC2. I have been trying to decide how to handle expiring sessions and what do I want the user experience to be when a session expires. From what I have found online, there are two common approaches. One is to deal with the issue on the client side, and the other is to deal with the issue on the server side.
In this post, I show you how I have chosen to deal with the issue on the server side.
In the end, I will deal with session expiration in the client and the server. I think I need to do this because the session can still go away without the client knowing about it, and so I still need to deal with session expiration in the server side / controller code.
I used the blog post made by Tyrone Davis to get me started. In my scenario, I am assuming that:
- there is no client side session detection in place (yet)
- the user takes an action on the screen that requests another view result to be returned
- the user IS NOT making an AJAX request or requesting a partial view (I will need to handle this later)
So here is my test code that shows how the action filter handles the return URL…
using System;
using NUnit.Framework;
using MvcContrib.TestHelper;
using System.Web.Mvc;
using TagCloud.Web.Framework.CustomActionFilters;
using Rhino.Mocks;
using System.Web;
using System.Collections.Specialized;
namespace TagCloud.Web.Framework.Test.CustomActionFilters
{
[TestFixture]
public class SessionExpireActionFilterAttributeTest
{
[TestCase((string)null, "~/Account/Logon")]
[TestCase("/SomeController/SomeAction", "~/Account/Logon?ReturnUrl=%2fSomeController%2fSomeAction")]
[TestCase("/aaa/bbb", "~/Account/Logon?ReturnUrl=%2faaa%2fbbb")]
public void ActionFilter_Redirects_WhenSessionExpires(string rawUrl, string expected)
{
request.Stub(it => it.RawUrl).Return(rawUrl);
filter.OnActionExecuting(filterContext);
filterContext.Result.AssertHttpRedirect().ToUrl(expected);
}
[SetUp]
public void Setup()
{
filter = new SessionExpireActionFilterAttribute();
filterContext = MockRepository.GeneratePartialMock<ActionExecutingContext>();
contextBase = MockRepository.GenerateStub<HttpContextBase>();
session = MockRepository.GenerateStub<HttpSessionStateBase>();
request = MockRepository.GenerateStub<HttpRequestBase>();
headers = new NameValueCollection();
headers["Cookie"] = "ASP.NET_SessionId";
contextBase.Stub(it => it.Session).Return(session);
contextBase.Stub(it => it.Request).Return(request);
request.Stub(it => it.Headers).Return(headers);
session.Stub(it => it.IsNewSession).Return(true);
filterContext.HttpContext = contextBase;
}
private HttpContextBase contextBase;
private SessionExpireActionFilterAttribute filter;
private NameValueCollection headers;
private HttpRequestBase request;
private HttpSessionStateBase session;
private ActionExecutingContext filterContext;
}
}
And here is my version of the ActionFilter…
using System;
using System.Web.Mvc;
using System.Web;
namespace TagCloud.Web.Framework.CustomActionFilters
{
public class SessionExpireActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var context = filterContext.HttpContext;
if (context.Session != null) {
if (context.Session.IsNewSession) {
string sessionCookie = context.Request.Headers["Cookie"];
if ((sessionCookie != null) && (sessionCookie.IndexOf("ASP.NET_SessionId") >= 0)) {
string redirectTo = "~/Account/Logon";
if (! string.IsNullOrEmpty(context.Request.RawUrl)) {
redirectTo = string.Format("~/Account/Logon?ReturnUrl={0}",
HttpUtility.UrlEncode(context.Request.RawUrl));
}
filterContext.Result = new RedirectResult(redirectTo);
}
}
}
base.OnActionExecuting(filterContext);
}
}
}
MVC Html.RenderAction vs. Html.RenderPartial?
I try to avoid “X vs. Y” or “this vs. that” ideological debates. I tend to think that the context of the current problem should dictate which forest you need to be in.
I think that my tendency is to favor RenderAction over RenderPartial. This may be due to my style, my bias toward quality, and test first development. For those of you that know me personally, I am a strong advocate of test first development and I try to follow “The Single Responsibility Principal”.
The fact that RenderAction allows you to call into an action method defined in a another controller not related to the controller of the parent view, allows the implementation of the partial view (or whatever is to be rendered) to be decoupled from its parent.
As you can see in the source code below, the RenderAction simply delegates to a controller that only deals with the partial view (not the parent). The RenderPartial method loads the partial view and injects data into the partial view.
Based on this observation, RenderAction promotes separation of concerns and loose coupling, and consequently, views that manifest themselves via RenderAction are inherently more testable.
That’s not to say that I would never use RenderPartial. If the markup in my main view was getting excessively large and the controller of the main view wasn’t already violating the single responsibility principal, and the parent view already had all of the data needed by the partial view, I would probably consider using RenderPartial over RenderAction.
If I had to build a quick down and dirty prototype, I may just consider RenderPartial. For me a prototype is throwaway code and is rarely tested and should never make it into production, so for this type of scenario I don’t mind skipping test first development.
Another benefit of a RenderPartial would be to test out some UI enhancements before the engineering team was truly committed to delivering the full blown production ready first class feature.
Some practical scenarios:
- my main menu would probably be loaded via RenderAction
- contact billing address, mailing address, general info, etc. would probably be loaded via RenderPartial
- sub menus / secondary navigation OR contextual navigation would probably be via RenderAction
- specialized web parts or mashup functions from external sources would probably be loaded via RenderAction
- simple master detail pages would probably be RenderPartial until they got overloaded with features
- simple views that allows uses to switch between read / edit / and create new would probably be done via RenderPartial
On page 198 of Dino Esposito’s MVC2 book he makes great arguments for RenderAction vs. RenderPartial. Basically he says that if the parent view already has all of the data, then consider RenderPartial because you already have the data and you wont end up doing all of the Context transfer operations and you can save some performance. However, if you need to go get the data or return something “other” than an .ascx view or possibly external data, then consider RenderAction.
Performance Note –> From what I understand, RenderAction issues an additional request, and RenderPartial simply injects the content into the existing request pipeline. So this could be a performance consideration.
Here is a quick snipped that outlines the syntactic difference in two approaches. Ultimately, the output on the screen is the same.
<h2>View Rendered via RenderPartial</h2>
<% Html.RenderPartial("CloudUserControl", new CloudUserControlViewModel
(this.ViewData["CloudTags"] as DisplayTags)); %>
<h2>View Rendered via RenderAction</h2>
<% Html.RenderAction<CloudUserControlController>(it => it.Index()); %>
This snippet outlines the C# code in the controller that handles the Html.RenderAction markup above…
public class CloudUserControlController : Controller
{
public ActionResult Index()
{
var viewModel = new CloudUserControlViewModel();
//TODO: go fetch some data
return PartialView("CloudUserControl", viewModel);
}
}
The images below show the sample output of the code above…
In any case, that’s my opinion… once again it depends, but I prefer the testability and single responsibility aspects of RenderPartial. Fortunately, there are other engineers that have have similar opinions (Derik Whittaker being one of them, thanks for your post).