DevExpress CallbackPanel PerformCallback hangs when content includes PageControl or TabControl - asp.net-mvc

I would like to refresh the tabs in my TabControl on demand, when a separate client action occurs on the page. I have placed my TabControl extension (tried PageControl also) within a CallbackPanel, and the EndCallBack event never fires. If ShowLoadingPanel is set to true, you see that the call is hanging because the loading panel never disappears. Both OnBeginCallback and the actual Controller callback action are executed. I assume there are some sort of conflicting callbacks occuring between the panel and tabs but I cannot figure out how to resolve it. If I replace the TabControl with basic html or other simpler DevExpress controls, everything works fine.
TabControl Partial (CallbackTestPageControl.cshtml):
#Html.DevExpress().TabControl(settings => {
settings.Name = "testTabControl";
settings.Width = Unit.Percentage(100);
settings.Tabs.Add("tab 1");
settings.Tabs.Add("tab 2");
settings.Tabs.Add("tab 3");
}).GetHtml()
Panel Partial (CallbackTestPanel.cshtml):
#Html.DevExpress().CallbackPanel(settings =>
{
settings.Name = "cbpTabStrip";
settings.CallbackRouteValues = new { Controller = "Home", Action = "CallbackTestPanel" };
settings.ClientSideEvents.BeginCallback = "OnBeginCallback";
settings.ClientSideEvents.EndCallback = "OnEndCallback";
settings.SetContent(() => Html.RenderPartial("CallbackTestPageControl"));
}).GetHtml()
View (CallbackTest.cshtml):
<script type="text/javascript">
var testId = null;
function ButtonClicked(s, e) {
alert('click');
testId = 1;
if (!cbpTabStrip.InCallback())
cbpTabStrip.PerformCallback();
}
function OnBeginCallback(s, e) {
alert('begin');
e.customArgs["Id"] = testId;
testId = null;
}
function OnEndCallback(s, e) {
alert('end');
if (testId != null)
cbpTabStrip.PerformCallback();
}
</script>
#Html.DevExpress().Button(settings => {
settings.Name = "CallbackButton";
settings.Text = "Callback";
settings.ClientSideEvents.Click = "ButtonClicked";
}).GetHtml()
#Html.Partial("CallbackTestPanel")
Controller (HomeController.cs):
public ActionResult CallbackTest()
{
return View();
}
public ActionResult CallbackTestPanel()
{
int id = !String.IsNullOrEmpty(Request.Params["Id"]) ? int.Parse(Request.Params["Id"]) : 0;
return PartialView("CallbackTestPanel");
}
ADDITIONAL INFO: Also, I have tried updating the DevExpress configuration in the web.config based on other suggestions online. Specifically - updating the enableResourceMerging attribute on the compression element to false rather than true. This seemed to allow the callback to end intermittantly. I really don't want to disable resource merging anyway, so I'm actually glad this didn't provide a reliable solution. So, this is what I currently have:
<devExpress>
<themes enableThemesAssembly="true" styleSheetTheme="" theme="Office2010Silver" />
<compression enableHtmlCompression="true"
enableCallbackCompression="true"
enableResourceCompression="true"
enableResourceMerging="true" />
<settings rightToLeft="false" />
<errors callbackErrorRedirectUrl="" />
</devExpress>

I'm sorry if I wasted anyone's time on this. In the end, the problem was that I had all my non-DevExpress scripts at the bottom of my layout body. I needed to move jQuery into the head prior to the DevExpress scripts. Strangely enough, everything else had been working fine prior to this issue. Thanks to anyone who tried to reproduce.

I know this is old but my issue is I had caching enabled that form i.e.
Once I removed that everything worked. Hope this helps someone else in the future.

Related

Using events to trigger ActionResult in ASP.NET MVC

I am working on an ASP.NET MVC web service. In a web page, when a user clicks on a button, this triggers a complex method that takes a bit of time to finish. I want to redirect the user to a waiting page and then, when the process is finished, to redirect the user to a new page.
When the process is done it raises an event, which I can listen to from the controller. But I cannot make the last step to work (the controller redirecting to the new page upon receiving the event).
Here is my very naïve attempt at doing it (with simpler names):
public MyController()
{
EventsControllerClass.ProcessComplete += new EventHandler<MyArgsClass>(OnEventReceived);
}
private void OnEventReceived(object sender, MyArgsClass eventArguments)
{
RedirectToPage();
}
private ActionResult RedirectToPage()
{
return RedirectToAction("PageName");
}
After many days working on this, I have a viable solution. It may not be pretty, but it works, and maybe some ideas can be useful for other people, so here it goes:
I will explain the solution to my particular problem: I need a button to redirect to a "waiting" page while a longer process runs in the background and raises an event when it is finished. When this event is received, we want to redirect the user (automatically) to a final page.
First, I created a class to listen to the event. I tried doing this directly in the controller, but you need to be careful about signing and unsigning, because apparently controllers get created and destroyed at each request. In this "listener class" I have a bool property that is set to "true" when the event is received.
When the first action is triggered, the controller normally redirects to the "wait" page, where I have this simple java script redirecting to the new action:
<script type="text/javascript">
window.location = "#Url.Action("WaitThenRedirect", "AuxiliaryControllerName")";
</script>
This sets in motion the long process (through another event). The key is that I do this with an asynchronous action (this controller inherits from AsyncController). (Note I used an auxiliary controller. This is to keep all asynchronous stuff apart.) This is how this looks (more info here):
public static event EventHandler<AuxiliaryEventsArgs> ProcessReady;
public void WaitThenRedirectAsync()
{
AsyncManager.OutstandingOperations.Increment();
ProcessReady += (sender, e) =>
{
AsyncManager.Parameters["success"] = e.success;
AsyncManager.OutstandingOperations.Decrement();
};
WaitForEvent();
}
public ActionResult WaitThenRedirectCompleted(bool success)
{
if (success)
{
return RedirectToAction("RedirectToView", "ControllerName");
}
else
{
return RedirectToAction("UnexpectedError", "ControllerName");
}
}
private void WaitForEvent()
{
bool isWaitSuccessful = true;
int waitingLoops = 0;
int waitingThreshold = 200;
int sleepPeriod = 100; // (milliseconds)
while (!EventsListener.IsTheThingReady())
{
System.Threading.Thread.Sleep(sleepPeriod);
++waitingLoops;
if (waitingLoops > waitingThreshold)
{
System.Diagnostics.Debug.WriteLine("Waiting timed out!");
isWaitSuccessful = false;
break;
}
}
isWaitSuccessful = true;
if (null != ProcessReady)
{
AuxiliaryEventsArgs arguments = new AuxiliaryEventsArgs();
arguments.success = isWaitSuccessful;
try
{
ProcessReady(null, arguments);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Error in event ProcessReady" + ex);
}
}
}
I believe it is possible to use ajax syntax for alternative solutions, but this is what I have and it works nicely. I believe this is not a very common need, but hopefully someone will benefit!

Dynamically assembling bundles from controller

I updating a custom CMS to be able to support multiple sites with different skins. I want to be able to store the skin dependent css files in a the database then load them in to a bundle once the CMS has determined which site is being loaded. This is what I have so far:
Controller:
public void loadResources(int portal_ID)
{
List<Portal_Res> res = cms.Portal_Resources.Where(r => r.portal_id == portal_ID).ToList<Portal_Res>();
BundleCollection portal_bundles = new BundleCollection();
List<Portal_Res> dsk_css = res.Where(r => r.type == "Desktop CSS").ToList<Portal_Res>();
StyleBundle bdl_dsk_css = new StyleBundle("~/Content/prtl_dsk_css");
if (dsk_css.Count > 0)
{
foreach (Portal_Res r in dsk_css)
{
bdl_dsk_css.Include(r.path);
}
}
portal_bundles.Add(bdl_dsk_css);
BundleConfig.RegisterBundles(portal_bundles);
}
View:
#Styles.Render("~/Content/prtl_dsk_css")
Rendered result:
<link href="/Content/prtl_dsk_css" rel="stylesheet"/>
None of the css files added to the bundle render in the page. Have checked that they are definitely being added to the bundle when the controller runs but not being rendered one the page.
Any ideas?
EDIT: Have inserted a breakpoint in the view and new bundles are not being pulled in with rest of them when the View renders. Don't know why.
Found solution:
public void loadResources(int portal_ID)
{
List<Portal_Res> res = cms.Portal_Resources.Where(r => r.portal_id == portal_ID).ToList<Portal_Res>();
List<Portal_Res> dsk_css = res.Where(r => r.type == "Desktop CSS").ToList<Portal_Res>();
StyleBundle bdl_dsk_css = new StyleBundle("~/Content/prtl_dsk_css");
if (dsk_css.Count > 0)
{
foreach (Portal_Res r in dsk_css)
{
bdl_dsk_css.Include(r.path);
}
}
BundleTable.Bundles.Add(bdl_dsk_css);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
Had to use BundleTable instead of new BundleCollection
Hope this is useful to someone else.

DevExpress MVC PivotGrid Custom Actions Keep Firing

I am using a DevExpress MVC Pivot Grid and trying to work out some problems with the loading and saving of layouts. So far I have the following:
I have set my CustomActionRouteValues in the PivotGridSettings as follows:
CustomActionRouteValues = new { Controller = "Home", Action = "PivotGridCustomCallback" },
Which points to the following:
public ActionResult PivotGridCustomCallback(string action, string reportName)
{
if (string.IsNullOrEmpty(reportName))
{
reportName = "Report 1";
}
var settings = PivotGridLayoutHelper.DefaultPivotGridSettings;
if (action == "Save")
{
// TODO: Find a better solution than this. At the moment, if Save is called once, it is then called again every time the user changes the layout.. which is why we have the 'saved' variable here.
bool saved = false;
settings.AfterPerformCallback = (sender, e) =>
{
if (saved)
{
return;
}
SaveLayout(((MVCxPivotGrid)sender).SaveLayoutToString(), reportName);
saved = true;
};
}
else if (action == "Load")
{
// TODO: Find a better solution than this. At the moment, if Load is called once, it is then called again every time the user changes the layout.. which is why we have the 'loaded' variable here.
bool loaded = false;
string layoutString = LoadLayout(reportName);
if (!string.IsNullOrEmpty(layoutString))
{
settings.BeforeGetCallbackResult = (sender, e) =>
{
if (loaded)
{
return;
}
((MVCxPivotGrid)sender).LoadLayoutFromString(layoutString, PivotGridWebOptionsLayout.DefaultLayout);
loaded = true;
};
}
}
ViewBag.PivotSettings = settings;
return PartialView("PivotPartial");
}
The problem, as you can see in the code comments, is that after performing an action just one time, it then gets called EVERY time I make any sort of change. So, for example... say I load a report.. that's fine.. but then when I try expand something or add a field.. or do ANYTHING, nothing seems to happen on the UI.. and I figured out that's because immediately, this code gets called again:
settings.BeforeGetCallbackResult = (sender, e) =>
{
((MVCxPivotGrid)sender).LoadLayoutFromString(layoutString, PivotGridWebOptionsLayout.DefaultLayout);
};
That just keeps resetting the values to the saved layout, which means the UI looks like it's unresponsive when trying to change anything.
This is why I now have the boolean variable called loaded to check if it's already loaded. That works.. but it's an ugly hack.. because it's making unnecessary trips to the server each and every time the user does anything on the pivot grid.
Surely there must be a way to prevent these actions from firing all the time?

How to pushasync from detailpage in masterdetailpage?

From the detailpage's view I try to push a new page on and I get this error: **
System.InvalidOperationException: Page must not already have a parent.
I keep trying different things but nothing works. Is there a way to push a page onto it, I mean, the detailpage is a navigationpage but it is a detailpage. Any and all help is much appreciated.
I am using xamarin forms labs ViewFactory.
//app.cs GetMainPage
var rootPage = ViewFactory.CreatePage<HomeVM>();
//in HomeView.xaml.cs, setting the detailpage to the list of messages
Detail = new NavigationPage(ViewFactory.CreatePage<MessagesVM>());
//This is in the MessagesView to show an individual message with a back button to the list of messages
Navigation.PushAsync(ViewFactory.CreatePage<MessageDetailVM>());
If you already have a NavigationPage, do not create another one to wrap your Detail instance in.
Detail = iewFactory.CreatePage<MessagesVM>();
Navigation.PushAsync(ViewFactory.CreatePage<MessageDetailVM>());
On my part, I also having the same error using MessagingCenter, but also solve it by unsubscribing/disposing after page closing/OnDisappearing.
Hope it helps.
public partial class MainPage : MasterDetailPage
{
public MainPage()
{
InitializeComponent();
MasterBehavior = MasterBehavior.Popover;
MessagingCenter.Subscribe<NavigationPage>(this, "Navigate", (pageItem) =>
{
Detail = pageItem;
IsPresented = false;
});
MessagingCenter.Subscribe<string>(this, "Logout", (s) =>
{
Application.Current.MainPage = new LoginPage("", "");
});
}
protected override void OnDisappearing()
{
MessagingCenter.Unsubscribe<NavigationPage>(this, "Navigate");
MessagingCenter.Unsubscribe<string>(this, "Logout");
base.OnDisappearing();
}
}
Unfortunately, I encountered this error again, and I solve it by using this setting the NavigationPage Parent property to null.
MessagingCenter.Subscribe<NavigationPage>(this, "Navigate", (pageItem) =>
{
pageItem.Parent = null; //solution
Detail = pageItem;
IsPresented = false;
});
Both answers before mine are pointing to the right direction, so this is just an addition. The key is in fact to not create the NavigationPage/NavigationView again.
In my project, I am using static objects for the MasterDetailPage, the NavigationView and the corresponding ViewModels in Xamarin Forms App class;
I am only creating an instance if their Value is null, which is likely to happen only if the app was closed before (no matter if closed by the user or the OS). If the app is still running (resumed), I am just using the already existing objects to restore the state.
This solved all these problems for me, and I hope it is helpful for someone else.
Try using the Navigation property of the Detail object like this:
Detail.Navigation.PushAsync(page);
To push a new page on my Detail I use the code below:
Note there I'm using a ListView with page options in MasterDetailPage
private void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var item = e.SelectedItem as MainMenuItem;
if (item == null)
return;
var page = (Page)Activator.CreateInstance(item.TargetType);
//Detail = new NavigationPage(page);
Detail.Navigation.PushAsync(page);
IsPresented = false;
MasterPage.ListView.SelectedItem = null;
}

EPiServer 7 MVC IDisplayModes

Trying to setup a Mobile Channel for use in Edit Mode in EPiServer 7.
Been following this link
http://world.episerver.com/Documentation/Items/Developers-Guide/EPiServer-CMS/7/Content/Display-Channels/
Created an Initialization module
[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class DisplayModesInitialization : IInitializableModule
{
public void Initialize(InitializationEngine context)
{
if (context.HostType == HostType.WebApplication)
{
System.Web.WebPages.DisplayModeProvider.Instance.Modes.RemoveAt(0);
context.Locate.DisplayChannelService()
.RegisterDisplayMode(new DefaultDisplayMode(RenderingTags.Mobile)
{
ContextCondition = (r) => r.Request.Browser.IsMobileDevice
});
}
}
public void Preload(string[] parameters) { }
public void Uninitialize(EPiServer.Framework.Initialization.InitializationEngine context) { }
}
As you can see I tried removing the existing "Mobile" display mode that exists to be replaced with the one created through the EPiServer DisplayChannelService().
Just browsing to the homepage works ok but when I force the userAgent to be a mobile browser it does hit the correct view... i.e. Index.mobile.cshtml
However it appears to still be looking for the _Layout.cshtml instead of _Layout.mobile.cshtml and even at that it fails to find it.
The file "~/Views/Shared/_Layout.cshtml" could not be rendered, because it does not exist or is not a valid page.
Anyone successfully create a mobile IDisplayMode for MVC through the EPiServer DisplayChannelService ?
Also if I explicitly set the layout in the mobile view
#{
Layout = "~/Views/Shared/_Layout.mobile.cshtml";
}
If fails to find that also ?
The file "~/Views/Shared/_Layout.mobile.cshtml" could not be rendered, because it does not exist or is not a valid page.
both the _Layout and _Layout.mobile DO exist in that location ?
Managed to get it working.
Discovered that _ViewStart.cshtml had the following set:
#{
Layout = "~/Views/Shared/_Layout.cshtml";
DisplayModeProvider.Instance.RequireConsistentDisplayMode = true;
}
So I removed the DisplayModeProvider.Instance.RequireConsistentDisplayMode = true; and it now works.
Not sure why this was causing the problem as there are both mobile and desktop views for the homepage and also mobile and desktop layouts ?

Resources