.NET MVC 3 Programmatically set layout - asp.net-mvc-3

In a .NET Razor Web Application i'm trying to programmatically set the Layout. I can not use _ViewStart.cshtml and don't wont to set the #{ Layout = "..." } on every page. This is what I have come up with:
A base WebViewPage class:
public abstract class SitePage<T> : System.Web.Mvc.WebViewPage<T>
{
private object _layout;
public new dynamic Layout { get { return _layout; } }
public override void InitHelpers()
{
base.InitHelpers();
_layout = "~/Themes/" + Settings.Theme + "/Views/_Layout.cshtml";
}
}
And in the application web.config I specify all view to use this base page. But the Layout is never used it seems. What could be wrong here?

The WebViewPage class inherits from WebPageBase that has a property named Layout like:
public override string Layout { get; set; }
You can override the Layout property, or change your _layout logic to achieve your purpose. For example:
public abstract class SitePage<T> : System.Web.Mvc.WebViewPage<T> {
// set this modifier as protected, to make it accessible from view-pages
protected string _layout{
get {
return base.Layout;
}
set {
base.Layout = value;
}
}
public override void InitHelpers() {
base.InitHelpers();
_layout = "~/Themes/" + Settings.Theme + "/Views/_Layout.cshtml";
}
}
and/or in a view-page, you can set it too:
#{
_layout = "_Your_Special_Layout.cshtml";
}
UPDATE: using a flag to avoid stack-over-flow in assigning _layout more that once:
public abstract class SitePage<T> : System.Web.Mvc.WebViewPage<T> {
public bool LayoutAssigned {
get {
return (ViewBag.LayoutAssigned == true);
}
set {
ViewBag.LayoutAssigned = value;
}
}
// set this modifier as protected, to make it accessible from view-pages
protected string _layout{
get {
return base.Layout;
}
set {
base.Layout = value;
}
}
public override void InitHelpers() {
base.InitHelpers();
if(!LayoutAssigned) {
_layout = "~/Themes/" + Settings.Theme + "/Views/_Layout.cshtml";
LayoutAssigned = true;
}
}
}

I tried to achieve the same just now by implementing a custom WebViewPage, however changing WebViewPage.Layout within my custom class didn't have any effect (as you have also discovered).
Eventually I ended up changing my _ViewStart.cshtml to have this code:
#{
this.Layout = this.Request.QueryString["print"] == "1"
? "~/Views/Layout/_Print.cshtml"
: "~/Views/Layout/_Layout.cshtml";
}
It might not be implemented how you wanted it, but it certainly does keep things dry and that is the main point.

Related

How to pass parameters and set properties while navigating to a new view model (initializeAsync)

I am using the design pattern mvvm, I have a view model locator and a view model base class.
The view model locator finds what view is associated to the view model. I also wrote a navigation service and one of my methods (NavigateTo) takes in a parameter (an object). the method is to navigate to a view model associated with the view.
namespace StudentData
{
public class StudentOverViewViewModel : ViewModelBase
{
private DataSet studentData;
public Icommand getDetails { get; set; }
public DataSet _data
{
get { return studentData; }
set
{
studentData = value;
RaisePropertyChanged(() => studentData);
}
}
public StudentOverViewViewModel (DataSet studentData)
{
this.studentData = studentData;
getDetails = new Command(Details);
}
public async Task getDetails()
{
// api calls done to retrieve data and set studentData to the current student data
await NavigationService.NavigateToAsync<StudentDetailViewModel>(studentData );
}
}
}
For the second view model I have :
namespace StudentData
{
public class StudentDetailViewModel: ViewModelBase
{
private DataSet Data;
public DataSet _Data
{
get
{
return Data;
}
set
{
Data= value;
RaisePropertyChanged(() => Data);
}
}
}
public StudentDetailViewModel(DataSet Data)
{
this.Data = Data;
}
public override async Task InitializeAsync(object navigationData)
{
if(navigationData is DataSet)
{
Data = (DataSet) navigationData; // after the page is initialized, the variables or properties/ models are not updated and is still null
}
}
}
My issue is that in my initializeAsync method in the second view model, I set the value and property for data, but after the method is done it set all the values back to null.
Thank you in advance for your help.
private async Task InternalNavigateToAsync(Type viewModelType, object
parameter)
{
Page page = CreatePage(viewModelType, parameter);
if (page is UserAuthenticateView)
{
Application.Current.MainPage = new CustomNiavigationView(page);
}
else
{
var navigationPage = Application.Current.MainPage as CustomNiavigationView;
if (navigationPage != null)
{
await navigationPage.PushAsync(page);
}
else
{
Application.Current.MainPage = new CustomNiavigationView(page);
}
}
await (page.BindingContext as ViewModelBase).InitializeAsync(parameter);
}

FreshMvvm PushNewNavigationServiceModal not working

I'm trying to change from FreshNavigationContainer to FreshMasterDetailNavigationContainer when the user is loggedin within the method SuccessfulLogin by using freshMvvm method PushNewNavigationServiceModal but nothing is happening.
public void SuccessfulLogin()
{
App.IsLoggedIn = true;
var masterDetailNav = new FreshMasterDetailNavigationContainer();
masterDetailNav.Init("Menu");
masterDetailNav.AddPage<ProfilePageModel>("Profile", null);
CoreMethods.PushNewNavigationServiceModal(masterDetailNav);
}
Edit :
I just noticed that after using this method navigation isn't working anymore.
You need to use CoreMethods.SwitchOutRootNavigation
First Setup your NavigationStacks
public class NavigationStacks
{
public static string LoginNavigationStack = "LoginNavigationStack";
public static string MainAppStack = "MainAppStack";
}
In you App.xaml.cs define the Navigations
FreshNavigationContainer loginMain;
FreshMasterDetailNavigationContainer masterDetailNav;
var loginPage = FreshPageModelResolver.ResolvePageModel<UserLoginPageModel>();
loginMain = new FreshNavigationContainer(loginPage, NavigationStacks.LoginNavigationStack);
var masterDetailNav = new FreshMasterDetailNavigationContainer(NavigationStacks.MainAppStack);
masterDetailNav.Init("Menu");
masterDetailNav.AddPage<ProfilePageModel>("Profile", null);
Then in your Login ViewModel
public void SuccessfulLogin()
{
App.IsLoggedIn = true;
CoreMethods.SwitchOutRootNavigation(NavigationStacks.MainAppStack);
}

Add resource/import image to custom UserControl attribute in VS Designer via Dialog(s)

My aim is to create a custom control displaying some images, which can be added/exchanged by the user of that control. So, if it is added to a Form, the GUI designer should be able to change some or all images provided by the control editing the appropriate attribute.
In my Test-Project I have a simple control with 4 Attributes:
public Image MyImage { get; set; } = null;
public List<int> MyListOfInt { get; set; } = new List<int>();
public List<Image> MyListOfImages { get; set; } = new List<Image>();
public ImageList MyImageList { get; set; } = new ImageList();
Using this control in a Windows Form Project, clicking on
MyImage brings up the 'Select resource' dialog. OK
MyListOfInt brings up the 'Int32 Collection Editor' dialog. OK
MyListOfImages brings up the 'Image Collection Editor' dialog, but using 'Add' button shows message:
'Cannot create an instance of System.Drawing.Image because it is an
abstract class.'
MyImageList shows an emtpy list, which cannot be edited.
My question is, if it's possible to tell VS Designer to use the 'Select resource' dialog when clicking 'Add' button and what needs to be done?
Starting from Marwie's comment, I was able to solve the problem.
There are three requirements that a collection should meet in order to be successfully persisted with the CollectionEditor:
The collection must implement the IList interface (inheriting from System.Collections.CollectionBase is in most of the cases the best option).
The collection must have an Indexer property.
The collection class must implement one or both of the following methods: Add and/or AddRange
So I created a class 'ImageItem' containing
an image
[Category("ImageItem")]
[DefaultValue(typeof(Image), null)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Image Picture {
get { return m_Picture; }
set { m_Picture = value; }
}
a name (optional)
[Category("ImageItem")]
[DefaultValue(typeof(string), "")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string Name {
get { return m_Name; }
set { m_Name = value; }
}
a value (optional)
[Category("ImageItem")]
[DefaultValue(typeof(int), "-1")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int Value {
get { return m_Value; }
set { m_Value = value; }
}
and a collection 'ImageCollection' holding instances of this class according to the conditions mentioned above:
public class ImageCollection : CollectionBase
public ImageItem this[int i]
public ImageItem Add(ImageItem item)
Then I created a control containing only this collection, initialized with one image:
public partial class MyControl: UserControl
{
public MyControl() {
InitializeComponent();
}
private ImageCollection m_MyImageCollection = new ImageCollection()
{ new ImageItem(0, "Failure", Properties.Resources.Cross), new ImageItem(1, "OK", Properties.Resources.Tickmark) };
[Browsable(true), Category("A Test"), DisplayName("Image Collection (ImageCollection)"), Description("Edit image collection")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Editor(typeof(System.ComponentModel.Design.CollectionEditor), typeof(System.Drawing.Design.UITypeEditor))]
public ImageCollection MyImageCollection {
get { return m_MyImageCollection; }
}
}
After compiling this code the designer shows that property. Now it is possible to add images using the common designer GUI controls.
I tried to change the default images compiled into this control when using it on my form, but I recognized, that the designer cannot remove content. It only stores the 'Add' action. So I modified the code to search within the collection for another item with the same ID. If there is one available, that instance is removed and replaced with the new one. Therefore I had to implement the AddRange method too.
public ImageItem Add(ImageItem item) {
for(int i = 0; i < InnerList.Count; i++) {
if(InnerList[i] is ImageItem) {
if(((ImageItem)InnerList[i]).Value == item.Value) {
InnerList.RemoveAt(i);
}
}
}
this.InnerList.Add(item);
return item;
}
public void AddRange(ImageItem[] array) {
foreach(ImageItem item in array) {
Add(item);
}
}
So my final classes are:
public class ImageItem {
private int m_Value = -1;
private string m_Name = "ImageItem";
private Image m_Picture = null;
[Category("ImageItem")]
[DefaultValue(typeof(int), "-1")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int Value {
get { return m_Value; }
set { m_Value = value; }
}
[Category("ImageItem")]
[DefaultValue(typeof(string), "")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string Name {
get { return m_Name; }
set { m_Name = value; }
}
[Category("ImageItem")]
[DefaultValue(typeof(Image), null)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Image Picture {
get { return m_Picture; }
set { m_Picture = value; }
}
public ImageItem() { }
public ImageItem(int value, string name, Image image) {
this.m_Value = value;
this.m_Name = name;
this.m_Picture = image;
}
}
And ImageCollection:
public class ImageCollection : CollectionBase {
public ImageCollection() {}
public ImageItem this[int i]
{
get { return (ImageItem)this.InnerList[i]; }
set { this.InnerList[i] = value; }
}
public ImageItem Add(ImageItem item) {
for(int i = 0; i < InnerList.Count; i++) {
if(InnerList[i] is ImageItem) {
if(((ImageItem)InnerList[i]).Value == item.Value) {
InnerList.RemoveAt(i);
}
}
}
this.InnerList.Add(item);
return item;
}
public void AddRange(ImageItem[] array) {
foreach(ImageItem item in array) {
Add(item);
}
}
public void Remove(ImageItem item) {
this.InnerList.Remove(item);
}
public bool Contains(ImageItem item) {
return this.InnerList.Contains(item);
}
public ImageItem[] GetValues() {
ImageItem[] item= new ImageItem[this.InnerList.Count];
this.InnerList.CopyTo(0, item, 0, this.InnerList.Count);
return item;
}
protected override void OnInsert(int index, object value) {
base.OnInsert(index, value);
}
}
I've got another answer from MSDN:
How to edit UserControl attribute of type ImageList in Designer PropertyGrid (add/remove/exchange images)
I will describe the idea in short. First create a new control with an ImageList attribute.
public partial class NewControl : UserControl {
public NewControl() {
InitializeComponent();
}
public ImageList MyImageList { get; set; } = null;
}
Then drag this control on any form.
Additionally drag an ImageList control from Toolbox onto this
form - I called it 'MyImages'.
Edit MyImages → Images with designer.
Assign 'MyImages' to NewControl's instance attribute MyImageList in property grid
The only drawback I see here is, that if the control already has an initialized ImageList attribute, the designer cannot handle it. If you try to edit MyImageList before you assigned another list, the designer shows the controls default list, that comes with the control. But it's not possible to edit that list.
This solution is much easier to deal with and much shorter than the first solution above, so that I prefer it more.

How to bind a property to an specific position of an array in a MvxViewModel?

Suppose I have a MvxViewModel with a string array property:
using System;
using Cirrious.MvvmCross.ViewModels;
namespace Foo {
public class FooViewModel : MvxViewModel {
private string[] mTexts;
public string[] Texts {
get { return mTexts; }
set {
mTexts = value;
RaisePropertyChanged(() => Texts);
}
}
public void Init()
{
Texts = new string[] { "foo", "bar" };
}
}
}
Suppose I also have an iOS MvxViewController with a single string property:
using System;
using Cirrious.MvvmCross.Touch.Views;
using Cirrious.MvvmCross.Binding.BindingContext;
namespace Foo {
public class FooView : MvxViewController {
public string Text { get; set; }
public override void ViewDidLoad () {
base.ViewDidLoad();
var bindset = this.CreateBindingSet<FooView, FooViewModel>();
bindset.Bind(Text).To("Texts[0]");
bindset.Apply();
}
}
}
How can I bind this string property, Text, to one position (say, 0) of the Texts array?
I have found sources (here and here) which indicate that this would be possible by what was done int the ViewDidLoad() method above, however, I get a warning: Unable to bind: source property source not found IndexedProperty:0 on String[].
What am I missing?
You can use a List or ObservableCollection instead of an Array:
private List<string> mTexts;
public List<string> Texts
{
get { return mTexts; }
set
{
mTexts = value;
RaisePropertyChanged(() => Texts);
}
}
I'm not to sure on Mvx support for Array type properties. I always tend to use an ObservableCollection or List. The error message you get when trying to bind to an Arrayseems to suggest it's struggling to do an index lookup on the Array.
Unable to bind: source property source not found IndexedProperty:0 on String[]
One approach that I tried and seemed to work was to implement an Indexer in the ViewModel and then bind to that.
ViewModel
string[] mTexts;
public string this[int index] => mTexts[index];
public void Init()
{
mTexts = new string[] { "foo", "bar" };
}
View Bindings
var bindset = this.CreateBindingSet<FooView, FooViewModel>();
bindset.Bind(Text).To(".[1]");
bindset.Apply();
As noted by xleon in the comments below, the period is optional. Using "[1]" will work as well.
Alternative with Lambda:
var bindset = this.CreateBindingSet<FooView, FooViewModel>();
bindset.Bind(Text).To(vm => vm[1]);
bindset.Apply();

How to use Razor Section multiple times in a View & PartialView (merge) without overriding it?

In the _Layout.cshtml file, I have a section at the bottom of the body called "ScriptsContent" declared like this:
#RenderSection("ScriptsContent", required: false)
In my view, I can then use this section to add scripts to be executed. But what if I also have a PartialView that also need to use this section to add additional scripts?
View
#section ScriptsContent
{
<script type="text/javascript">
alert(1);
</script>
}
#Html.Partial("PartialView")
PartialView
#section ScriptsContent
{
<script type="text/javascript">
alert(2);
</script>
}
Result
Only the first script is rendered. The second script doesn't exist in source code of the webpage.
Razor seems to only output the first #section ScriptsContent that it sees. What I would like to know is if there's a way to merge each call to the section.
If we cannot do this, what do you propose?
Here's a solution for that problem. It's from this blog: http://blog.logrythmik.com/post/A-Script-Block-Templated-Delegate-for-Inline-Scripts-in-Razor-Partials.aspx
public static class ViewPageExtensions
{
private const string SCRIPTBLOCK_BUILDER = "ScriptBlockBuilder";
public static MvcHtmlString ScriptBlock(this WebViewPage webPage, Func<dynamic, HelperResult> template)
{
if (!webPage.IsAjax)
{
var scriptBuilder = webPage.Context.Items[SCRIPTBLOCK_BUILDER] as StringBuilder ?? new StringBuilder();
scriptBuilder.Append(template(null).ToHtmlString());
webPage.Context.Items[SCRIPTBLOCK_BUILDER] = scriptBuilder;
return new MvcHtmlString(string.Empty);
}
return new MvcHtmlString(template(null).ToHtmlString());
}
public static MvcHtmlString WriteScriptBlocks(this WebViewPage webPage)
{
var scriptBuilder = webPage.Context.Items[SCRIPTBLOCK_BUILDER] as StringBuilder ?? new StringBuilder();
return new MvcHtmlString(scriptBuilder.ToString());
}
}
so anywwhere in your View or PartialView you can use this:
#this.ScriptBlock(
#<script type='text/javascript'>
alert(1);
</script>
)
and in your _Layout or MasterView, use this:
#this.WriteScriptBlocks()
There is no way to share sections between a view and partial views.
Absent a ScriptManager-like solution, you could have a collection of script files (initialized in your view and stored either in HttpContext.Items or in ViewData) to which the partial view would append the script file names it requires. Then towards the end of your view you would declare a section that fetches that collection and emits the right script tags.
The problem with the accepted answer is that it breaks Output Caching. The trick to solving this is to overwrite the OutputCache attribute with your own implementation. Unfortunately we can't extend the original attribute since it has lots of internal methods which we need to access.
I actually use Donut Output Caching which overwrites the OutputCache attribute itself. There are alternative libraries which also use their own OutputCache attribute so I will explain the steps I made to get it to work so that you can apply it to whichever one you're using.
First you need to copy the existing OutputCache attribute and place it within your application. You can get the existing attribute by looking at the source code.
Now add the following property to the class. This is where we store the script blocks so we can render the correct ones when retrieving from the cache.
public static ConcurrentDictionary<string, StringBuilder> ScriptBlocks = new ConcurrentDictionary<string, StringBuilder>();
Now inside the OnActionExecuting method you need to store the cache key (the unique identifier for the output cache) inside the current requests collection. For example:
filterContext.HttpContext.Items["OutputCacheKey"] = cacheKey;
Now modify the ViewPageExtensions class by adding the following (replacing CustomOutputCacheAttribute with the name of your attribute):
var outputCacheKey = webPage.Context.Items["OutputCacheKey"] as string;
if (outputCacheKey != null)
CustomOutputCacheAttribute.ScriptBlocks.AddOrUpdate(outputCacheKey, new StringBuilder(template(null).ToHtmlString()), (k, sb) => {
sb.Append(template(null).ToHtmlString());
return sb;
});
before:
return new MvcHtmlString(string.Empty);
Note: For a slight performance boost you'll also want to make sure you only call "template(null).ToHtmlString()" once.
Now return to your custom OutputCache attribute and add the following only when you are retrieving from the cache inside the OnActionExecuting method:
if (ScriptBlocks.ContainsKey(cacheKey)) {
var scriptBuilder = filterContext.HttpContext.Items["ScriptBlockBuilder"] as StringBuilder ?? new StringBuilder();
scriptBuilder.Append(ScriptBlocks[cacheKey].ToString());
filterContext.HttpContext.Items["ScriptBlockBuilder"] = scriptBuilder;
}
Here's the final code of my attribute:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.UI;
using DevTrends.MvcDonutCaching;
public class CustomOutputCacheAttribute : ActionFilterAttribute, IExceptionFilter {
private readonly IKeyGenerator _keyGenerator;
private readonly IDonutHoleFiller _donutHoleFiller;
private readonly IExtendedOutputCacheManager _outputCacheManager;
private readonly ICacheSettingsManager _cacheSettingsManager;
private readonly ICacheHeadersHelper _cacheHeadersHelper;
private bool? _noStore;
private CacheSettings _cacheSettings;
public int Duration { get; set; }
public string VaryByParam { get; set; }
public string VaryByCustom { get; set; }
public string CacheProfile { get; set; }
public OutputCacheLocation Location { get; set; }
public bool NoStore {
get { return _noStore ?? false; }
set { _noStore = value; }
}
public static ConcurrentDictionary<string, StringBuilder> ScriptBlocks = new ConcurrentDictionary<string, StringBuilder>();
public DonutOutputCacheAttribute() {
var keyBuilder = new KeyBuilder();
_keyGenerator = new KeyGenerator(keyBuilder);
_donutHoleFiller = new DonutHoleFiller(new EncryptingActionSettingsSerialiser(new ActionSettingsSerialiser(), new Encryptor()));
_outputCacheManager = new OutputCacheManager(OutputCache.Instance, keyBuilder);
_cacheSettingsManager = new CacheSettingsManager();
_cacheHeadersHelper = new CacheHeadersHelper();
Duration = -1;
Location = (OutputCacheLocation)(-1);
}
public override void OnActionExecuting(ActionExecutingContext filterContext) {
_cacheSettings = BuildCacheSettings();
var cacheKey = _keyGenerator.GenerateKey(filterContext, _cacheSettings);
if (_cacheSettings.IsServerCachingEnabled) {
var cachedItem = _outputCacheManager.GetItem(cacheKey);
if (cachedItem != null) {
filterContext.Result = new ContentResult {
Content = _donutHoleFiller.ReplaceDonutHoleContent(cachedItem.Content, filterContext),
ContentType = cachedItem.ContentType
};
if (ScriptBlocks.ContainsKey(cacheKey)) {
var scriptBuilder = filterContext.HttpContext.Items["ScriptBlockBuilder"] as StringBuilder ?? new StringBuilder();
scriptBuilder.Append(ScriptBlocks[cacheKey].ToString());
filterContext.HttpContext.Items["ScriptBlockBuilder"] = scriptBuilder;
}
}
}
if (filterContext.Result == null) {
filterContext.HttpContext.Items["OutputCacheKey"] = cacheKey;
var cachingWriter = new StringWriter(CultureInfo.InvariantCulture);
var originalWriter = filterContext.HttpContext.Response.Output;
filterContext.HttpContext.Response.Output = cachingWriter;
filterContext.HttpContext.Items[cacheKey] = new Action<bool>(hasErrors => {
filterContext.HttpContext.Items.Remove(cacheKey);
filterContext.HttpContext.Response.Output = originalWriter;
if (!hasErrors) {
var cacheItem = new CacheItem {
Content = cachingWriter.ToString(),
ContentType = filterContext.HttpContext.Response.ContentType
};
filterContext.HttpContext.Response.Write(_donutHoleFiller.RemoveDonutHoleWrappers(cacheItem.Content, filterContext));
if (_cacheSettings.IsServerCachingEnabled && filterContext.HttpContext.Response.StatusCode == 200)
_outputCacheManager.AddItem(cacheKey, cacheItem, DateTime.UtcNow.AddSeconds(_cacheSettings.Duration));
}
});
}
}
public override void OnResultExecuted(ResultExecutedContext filterContext) {
ExecuteCallback(filterContext, false);
if (!filterContext.IsChildAction)
_cacheHeadersHelper.SetCacheHeaders(filterContext.HttpContext.Response, _cacheSettings);
}
public void OnException(ExceptionContext filterContext) {
if (_cacheSettings != null)
ExecuteCallback(filterContext, true);
}
private void ExecuteCallback(ControllerContext context, bool hasErrors) {
var cacheKey = _keyGenerator.GenerateKey(context, _cacheSettings);
var callback = context.HttpContext.Items[cacheKey] as Action<bool>;
if (callback != null)
callback.Invoke(hasErrors);
}
private CacheSettings BuildCacheSettings() {
CacheSettings cacheSettings;
if (string.IsNullOrEmpty(CacheProfile)) {
cacheSettings = new CacheSettings {
IsCachingEnabled = _cacheSettingsManager.IsCachingEnabledGlobally,
Duration = Duration,
VaryByCustom = VaryByCustom,
VaryByParam = VaryByParam,
Location = (int)Location == -1 ? OutputCacheLocation.Server : Location,
NoStore = NoStore
};
} else {
var cacheProfile = _cacheSettingsManager.RetrieveOutputCacheProfile(CacheProfile);
cacheSettings = new CacheSettings {
IsCachingEnabled = _cacheSettingsManager.IsCachingEnabledGlobally && cacheProfile.Enabled,
Duration = Duration == -1 ? cacheProfile.Duration : Duration,
VaryByCustom = VaryByCustom ?? cacheProfile.VaryByCustom,
VaryByParam = VaryByParam ?? cacheProfile.VaryByParam,
Location = (int)Location == -1 ? ((int)cacheProfile.Location == -1 ? OutputCacheLocation.Server : cacheProfile.Location) : Location,
NoStore = _noStore.HasValue ? _noStore.Value : cacheProfile.NoStore
};
}
if (cacheSettings.Duration == -1)
throw new HttpException("The directive or the configuration settings profile must specify the 'duration' attribute.");
if (cacheSettings.Duration < 0)
throw new HttpException("The 'duration' attribute must have a value that is greater than or equal to zero.");
return cacheSettings;
}
}
I also had to modify the Donut Output Cache library to make IExtendedOutputCacheManager and the OutputCacheManager constructor public.
Please note this has been extracted from my application and may require some minor tweaks. You should also place WriteScriptBlocks at the bottom of the page so it is not called until after all child actions are triggered.
Hope this helps.

Resources