ASP.NET: Call function in MasterPage through UserControl - c#

Calling a function from the MasterPage in a Page is quite straigt forward but how do I call it for a UserControl:
Adding <%# MasterType VirtualPath="~/MasterPage.master" %>, doesn't work on UserControls.
So this.Page.Master.MyFunction() fails :(

You have to cast the this.Page.Master first as the Master property of the Page is of type System.Web.UI.MasterPage.
e.g.
((MyMaster)this.Page.Master).MyFunction();
You could check the type of the underlying master page by adding a property to the code behind of the user control:
public string MType
{
get { return this.Page.Master.GetType().FullName; }
}
and print the result out in the User control markup, e.g. add this line to make it print out as a comment in the source code:
<!-- <%= MType %> //-->

Niels,
Leverage reflection (as suggested by JDunkerley) is one approach to the problem. Another you might consider is implementing an interface:
Create an interface that includes your method.
Implement the interface in your master page
From your control, reference this.Page.Master via the interface type.
Call your method.
This is a better OO approach, leads to less coupling, and will certainly perform better than runtime reflection.
I hope this helps!

You are couppling your code very thighly if you call a function on the masterpage from within your user control.
The Control can only be used on pages that are based on that master. I think this is usually a bad design, and it will violate at least the law of demeter.
What exactly do you want to accomplish in your control?

JDunkerley has it right. But allow me to explain how to decouple it using MVP so you can work toward avoiding the design issue Heiko Hatzfeld is talking about.
Basically, implement the MVP pattern for both your control and your master page. See here for instructions on how to do that. Declare the method you want to call in the master's interface (IMasterView). Next create a class that will control the relationship between the two components; we'll call it the PageController class. Place an instance of this class in request state for each request by adding the following line to global.asax.cs:
/* global.asax.cs */
protected void Application_BeginRequest(object sender, EventArgs e)
{
// ...
HttpContext.Current.Items["Controller"] = new PageController();
// ...
}
You can then access this instance from each of the presenters (master and control) via the following line of code:
var controller = HttpContext.Current.Items["Controller"] as PageController;
You can then implement an event or some other mechanism to allow the control to invoke the method on the master in a decoupled manner through this shared object. For example:
/* PageController.cs */
public event EventHandler SomeEvent;
protected virtual void OnSomeEvent(EventArgs e)
{
Debug.Assert(null != e);
var handler = this.SomeEvent;
if (null != handler)
handler(this, e);
}
public void FireSomeEvent()
{
this.OnSomeEvent(EventArgs.Empty);
}
/* ControlPresenter.cs */
public ControlPresenter(IControlView view)
: base()
{
view.EventFired += (sender, e) =>
{
var controller = HttpContext.Current.Items["Controller"] as PageController;
controller.FireSomeEvent();
};
}
/* MasterPresenter.cs */
public MasterPresenter (IMasterView view)
: base()
{
var controller = HttpContext.Current.Items["Controller"] as PageController;
controller.SomeEvent += (sender, e) => view.MyFunction();
}
Make sure the "EventFired" event is declared in your control's interface (IControlView) and implemented in the control. Then all you have to do to affect the master (call its method), is fire this event and the MVP + the PageContoller will take care of the rest.
Cheers

I couldn't get the above answers to work, so here is what worked for me:
You want to reference a master page property from a user control.
Firstly, your master page will have a public property like so :
public string BodyClass
{
set
{
this.masterBody.Attributes.Add("class", value);
}
}
Now add a reference to the master page in the user control ASCX file like so :
<%# Register Src="~/Source/MasterPages/Main.master" TagPrefix="MSTR" TagName="MasterPage" %>
Then in the code behind (C# in my case) you have this code :
Main masterPage = (Main)this.Page.Master;
masterPage.BodyClass = "container";
Without the reference to the master page above your user control will not be able to find the master page class.

Related

Object disposing in Xamarin.Forms

I'm looking for the right way to dispose objects in a Xamarin Forms application. Currently i'm using XAML and MVVM coding style. Then from my view model i get a reference to a disposable object through the builtin service locator (DependencyService). Ideally i should be able to call Dispose() on the objects from my view model, but other solutions like attaching to ContentPage.OnDisappearing and NavigationPage.Popped could be feasible.
I had pretty much the same requirement a couple of weeks ago. I wanted to make sure that event subscriptions in my view models would be unsubscribed when the page is closed. After a lot of research my conclusion was that the simplest solution was to use the ContentPage.OnDisappearing method.
As you pointed out the object you want to dispose is in your ViewModel, so you need a little bit of infrastructure to make sure your ViewModel is informed when the it's disappearing. To do that I defined a base implementation of my view model that had two key methods OnAppearing and OnDisappearing (note this was a class rather than an interface because I have other base functionality such as IPropertyNotify implementation - not shown here).
public class ViewModelBase
{
/// <summary>
/// Called when page is appearing.
/// </summary>
public virtual void OnAppearing()
{
// No default implementation.
}
/// <summary>
/// Called when the view model is disappearing. View Model clean-up should be performed here.
/// </summary>
public virtual void OnDisappearing()
{
// No default implementation.
}
}
Then I subsclassed ContentPage and override the OnAppearing and OnDisappearing methods and then use them to notify my view model.
public class PageBase : ContentPage
{
/// <summary>
/// Performs page clean-up.
/// </summary>
protected override void OnDisappearing()
{
base.OnDisappearing();
var viewModel = BindingContext as ViewModelBase;
// Inform the view model that it is disappearing so that it can remove event handlers
// and perform any other clean-up required..
viewModel?.OnDisappearing();
}
protected override void OnAppearing()
{
base.OnAppearing();
// Inform the view model that it is appearing
var viewModel = BindingContext as ViewModelBase;
// Inform the view model that it is appearing.
viewModel?.OnAppearing();
}
}
Then when you implement a page just make sure that it is of type PageBase:
<?xml version="1.0" encoding="utf-8" ?>
<pages:PageBase xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Forms.App.Controls;assembly=Forms.App"
xmlns:converters="clr-namespace:Forms.App.Converters;assembly=Forms.App"
xmlns:pages="clr-namespace:Forms.App.Pages;assembly=Forms.App"
x:Class="Forms.App.Pages.LogonPage"
NavigationPage.HasNavigationBar="False"
Title="Logon">
And in your ViewModel you can then override your OnDisappearing method and dispose your objects:
public class FormViewModel : ViewModelBase
{
public override void OnDisappearing()
{
base.OnDisappearing();
// Dispose whatever objects are neede here
}
}
Just one thing to watch out for - if you're using stack navigation the OnDisappearing method gets called when you stack another page on-top of your current page (your page is disappearing temporarily after all). So you will need to cater for this and probably not dispose your object in that case. However if you're not stacking anything on-top of your page there is nothing to worry about. In my case it was just event subscriptions so I attached the event handlers in the OnAppearing and detached them on the OnDisappearing.
I hope that helps you out!
We were getting disposed of object exceptions in Forms when Bindings to ListViews or Labels changed values as pages/fragments were being disposed of. I'm assuming you could dispose of objects in your ViewModel the same place we were removing bindings.
protected override void OnParentSet()
{
base.OnParentSet();
if (Parent == null)
{
//Clear a bunch of bindings or dispose of ViewModel objects
BindingContext =
_listView.ItemsSource = null;
}
}
I have View Models that conform to IDisposable. So I needed a way for the Page's BindingContext to be disposed when the page is no longer needed.
I used the suggestion of Nick which uses OnParentSet being set to NULL to known when the page is no longer needed.
The SafeContentPage class can be used in place of ContentPage. Iff The binding context supports IDisposable will it automatically try to dispose the binding context.
public class SafeContentPage : ContentPage
{
protected override void OnParentSet()
{
base.OnParentSet();
if (Parent == null)
DisposeBindingContext();
}
protected void DisposeBindingContext()
{
if (BindingContext is IDisposable disposableBindingContext) {
disposableBindingContext.Dispose();
BindingContext = null;
}
}
~SafeContentPage()
{
DisposeBindingContext();
}
}
The OnDisappearing method isn't a reliable technique as there are platform differences in terms of when it is called, and just because the page disappeared doesn't mean its View Model is no longer needed.

BindableProperty in custom view does not unsubscribe PropertyChanged

Background info
I'm developing a Xamarin Forms (v4.1.1.3, testing on iOS) application in XAML, using MVVM with a View first approach; I'm assigning single-instance ViewModels to Views by using the ViewModelLocator service of MVVMLight:
BindingContext="{Binding [SearchViewModel], Source={StaticResource ViewModelLocator}}"
When navigating to another page, I'm constructing a new instance of the page, which will receive the very same ViewModel instance every time.
var page = new SearchView();
var tabbedPage = Application.Current.MainPage as TabbedPage;
if (tabbedPage != null)
await tabbedPage.CurrentPage.Navigation.PushAsync(page);
The issue
I've implemented a custom control (view?), that is supposed to show search results in a tile-like layout. This control is created when navigating from a search NavigationPage to a search results ContentPage.
Every time I return to the search page and navigate back to search results, the view is reconstructed and the PropertyChanged of the BindableProperties are subscribed. These PropertyChanged events are never unsubscribed, so every time I navigate to the search results view and change the bound ViewModel property, the event is fired increasingly multiple times.
In the following code the OnItemsPropertyChanged is triggered multiple times, based on how many times I've navigated from the search view to the search results view:
public class WrapLayout : Grid
{
public static readonly BindableProperty ItemsProperty =
BindableProperty.Create("Items", typeof(IEnumerable), typeof(WrapLayout), null, propertyChanged: OnItemsPropertyChanged);
public IEnumerable Items
{
get { return (IEnumerable)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public WrapLayout()
{
...
}
private static void OnItemsPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
...
}
}
My questions:
Shouldn't the BindableProperty unsubscribe from PropertyChanged and -Changing by itself?
Does this occur because of the way I associated Views with ViewModels and/or navigate through pages?
Should I handle unsubscribing these events myself, and how?
EDIT; additional navigation info
I have a MainView TabbedPage, which creates SearchView as NavigationPage:
public MainView()
{
InitializeComponent();
Children.Add(new NavigationPage(new SearchView())
{
Title = AppResources.Tab_Search,
Icon = "tab_search"
});
}
SearchView has, upon creation, a single-instance ViewModel assigned by the ViewModelLocator that was mentioned at the start of this topic, using MVVMLight's SimpleIoc container.
When a search command in SearchView is fired, I send a request to an API which returns search results. These results are displayed on another page, to which I navigate to from the SearchView's ViewModel:
await _navigationService.NavigateTo(ViewModelLocator.PageKeyFileResults, searchResult);
Which functionality looks somewhat like this:
public async Task NavigateTo(string pagekey, object viewModelParameter)
{
var constructor = _pagesByKey[pagekey].Constructor; //Gets the Func<Page> that simple creates the requested page, without using reflection.
var page = constructor() as Page;
var viewModel = page.BindingContext as BaseViewModel;
if (viewModel != null)
viewModel.Initialize(viewModelParameter);
var tabbedPage = Application.Current.MainPage as TabbedPage;
if (tabbedPage != null)
await tabbedPage.CurrentPage.Navigation.PushAsync(page);
else
await Application.Current.MainPage.Navigation.PushAsync(page);
}
The constructed page looks somewhat like:
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Views.FileResultsView"
xmlns:pages="clr-namespace:Views.Pages;assembly=Views"
xmlns:controls="clr-namespace:Views.Controls;assembly=Views"
BindingContext="{Binding [FileResultsViewModel], Source={StaticResource ViewModelLocator}}">
<ScrollView>
<controls:WrapLayout
Items="{Binding SearchResults}" />
</ScrollView>
</pages:BaseContentPage>
Where BaseContentPage is:
public class BaseContentPage : ContentPage
{
protected override void OnAppearing()
{
base.OnAppearing();
MessagingCenter.Subscribe<DialogMessage>(this, "ShowDialog", (dialogMessage) =>
{
if (string.IsNullOrWhiteSpace(dialogMessage.AcceptButton))
DisplayAlert(dialogMessage.Title, dialogMessage.Content, dialogMessage.CancelButton);
else
DisplayAlert(dialogMessage.Title, dialogMessage.Content, dialogMessage.AcceptButton, dialogMessage.CancelButton);
});
}
protected override void OnDisappearing()
{
base.OnDisappearing();
MessagingCenter.Unsubscribe<DialogMessage>(this, "ShowDialog");
}
}
And where ViewModel is basically like this:
public class FileResultsViewModel : BaseViewModel
{
private IEnumerable<ASRow> _searchResults;
public IEnumerable<ASRow> SearchResults
{
get { return _searchResults; }
set { Set(ref _searchResults, value); }
}
internal override void Initialize(object parameter)
{
base.Initialize(parameter);
if (parameter is AdvancedSearchResponse)
{
var searchResults = parameter as AdvancedSearchResponse;
SearchResults = new List<ASRow>(searchResults.Rows);
}
}
}
Shouldn't the BindableProperty unsubscribe from PropertyChanged and -Changing by itself?
Yes - it should. If it does not it is most certainly a bug
Does this occur because of the way I associated Views with ViewModels and/or navigate trough pages?
That is most likely also an option, since i didn't experience the behaviour you described yet. You would need to share more of your surrounding setup code.
Should I handle unsubscribing these events myself, and how?
It's hard for you to always control unsubscribing, since most of the time it will be the control subscribing to events (unless you do it yourself, in which case it's always your duty to unsub again)
While it is ugly it's sometimes necessary to get a quick workaround, which in your case would be browsing how xamarin holds a list of the change delegates and manually unsubscribe them on page appearing for example.
I hope that answers your question. Feel free to comment if it does not.
Update
In your case i would debug your page base, and verify wether or not
OnDisappearing is called correctly
Your handler is gone after unsubscribe
(This is lazy but i usually unsub an event before subbing it, just to make sure such a bug does not happen, because most EventManagement services won't throw if you're trying to unsub a handler which is not registered.)
at least that's the most likely causes of your issue.
Shouldn't the BindableProperty unsubscribe from PropertyChanged and -Changing by itself?
No. The Binding class takes care of this. Not the BindableProperty.
Does this occur because of the way I associated Views with ViewModels and/or navigate through pages?
You are seeing this because you are forgetting that the Navigation Stack keeps a list of pages in memory. Since multiple pages are pointing to the same BindingContext, there are multiple observers to changes. You would not have this particular issue if you didn't re-use View Models.
Should I handle unsubscribing these events myself, and how?
No. If it is really a concern then set BindingContext to null when a page disappears, and then restore it when reappearing. Keep in mind though that this still has cost to it, especially if your UI is really busy and has lots of dynamic content that is controlled by data bindings.

How to code reuse apsx?

I know in C# I can make a factory but I don't not know how to reuse code in aspx. My code was originally purposed for ARList only, but now has IcnList. I thought about making a switch statement but isn't there something better to code?
The Function
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
ARExtractionController arController = new ARExtractionController();
Dictionary<int, string> ARDictionary = arController.GetTickets();
List<int> sortedARList = new List<int>();
foreach (KeyValuePair<int, string> kv in ARDictionary)
{
sortedARList.Add(kv.Key);
}
sortedARList.Sort();
sortedARList.Reverse();
ARList.DataSource = sortedARList;
ARList.DataBind();
ARList.Items.Insert(0, " ");
ARList.AutoPostBack = true;
}
}
If you just want to be able to reuse the code, add a class to your project for utility methods and dump it in there for both pages to use.
If you're trying to reuse code with and associated UI, look into User Controls (ascx files) which allow you to do just that.
It isn't clear which is right for you from the question.
In TicketExtractionWeb, create a BasePage class.
BasePage.cs:
public class BasePage : System.Web.UI.Page
{
// Add methods that are used on all your pages in here.
protected DateTime GetCurrentDate()
{
return DateTime.Now;
}
}
And a page (we'll call it MyPage):
public class MyPage : BasePage
{
protected Page_Load(object sender, EventArgs e)
{
var currentDate = this.GetCurrentDate();
}
}
So when you create another aspx page, it will default to:
public class MyNewPage : System.Web.UI.Page
{
// ...
}
Just change : System.Web.UI.Page to : BasePage
You should be able to inherit from a single page to reuse the code.
Top of aspx file:
<%# Page Language="C#" AutoEventWireup="true" Inherits="MyNamespace.CommonCode" %>
If you want to reuse html , please use a user control file (.ascx) , which can be reused muliple times
when do you need .ascx files and how would you use them?

C# - Easy way to execute code in all pages in the Page_Load method

I'm looking for a way to add some code to execute in all Page_Load events on all pages of my web application, without have to write it in all the pages.
The code must execute before the Page_Load methods on the pages.
Thanks for the attention.
You can create one Class let say BasePage.cs, and here you will have one virtual method Page_Load:
public class BasePage: System.Web.UI.Page
{
protected virtual void Page_Load(object sender, EventArgs e)
{
//Some logic here that you want to execute for all pages
}
}
Then, in every page where you want to execute this code on PageLoad, make that page to inherit from BasePage and override the PageLoad method, like this:
in file somePage.aspx.cs do this:
public partial class somePage : BasePage
{
protected override void Page_Load(object sender, EventArgs e)
{
base.Page_Load(sender, e); //This line will execute page load from BasePage class
//The rest of code you want to execute on this page load
}
}
You could create a master page, set all the pages you want the code to be executed as "childs" of your master page, and them put the code you want to be executed on the Page_Load event of your master page.
To see how master pages work: http://msdn.microsoft.com/en-us/library/ie/wtxbf3hh.aspx

How to add a session value in a asp.net handler page and access that in other page

I am setting a session value in the Handler page and want to use that in other page, when I access the session value in other page I get the error saying Object reference not set to an instance of an object.
string ad=Session["StackOverflow"].ToString();
How to access the session value in c# .aspx page
public class Upload : IHttpHandler, IRequiresSessionState{
public void ProcessRequest (HttpContext context) {
context.Session.Add("StackOverflow",filename);
}}
What you have there should work except it is worth noting that you need to include an implementation of the IRequiresSessionState interface.
using System.Web.Sessionstate;
public class Handler: IHttpHandler, IRequiresSessionState
{
public void ProcessRequest(HttpContext context)
{
context.Session["YourSessionVar"] = yourStringVar;
}
}
EDIT: OP edited question after post
In order to access the variable in your other page be sure to implement IRequiresSessionState on that page too. If you fail to do this you will not have access to the session variables.
EDIT: Futher info requested by OP
In order to access the session variable on your aspx page do the following:
using Sytem.Web.SessionState;
public class YourClass : IRequiresSessionState
{
public string MyVar;
protected void Page_Load(object senser, EventArgs e)
{
MyVar = Session["YourSessionVarName"].ToString();
}
}
Now to add this into your onclick function in the html/aspx page you do this
<div onclick="yourJScriptFunction('<% Response.Write(MyVar) %>');">

Categories