ASP.NET: Best way to customize MasterPage driven by Page - c#

I have quite simple site structure with one mastepage and a bunch of pages. The mastepage is quite advanced though and I need from the page be able to control certain aspects of the Masterpage.
I want to be able to enter these directive in the aspx file to not clutter the code behind files.
My idea was to create different "directive" user controls such as SeoDirective:
using System;
public partial class includes_SeoDirective : System.Web.UI.UserControl
{
public string Title { get; set; }
public string MetaDescription { get; set; }
public string MetaKeywords { get; set; }
}
I include this directive in those pages that need to override the default mastepage settings.
<offerta:SeoDirective runat="server" Title="About Us" MetaDescription="Helloworld"/>
In my masterpage I look if there's any directives:
includes_SeoDirective seo = (includes_SeoDirective) ContentPlaceHolder1.Controls.Children().FirstOrDefault(e => e is includes_SeoDirective);
(Children() is an extension so I can work with Linq on a ControlCollection)
Now to my question: I'm not to happy about this solution might be a bit bloated?
I'm looking for alternative solutions where I can created these tags in the aspx file.
I've looked at the trick where I extend the Page, but that requiries we to modify the VS configs for the project to compile, so I dropped that solutions.

As far as I am aware, there isn't a standard way of doing this. I have done this same thing in the past in much the same way you have, except I used an interface on the pages that I needed the master page to look for, which defined a method it could call to do specific logic with the master page.
You might be able to use this same paradigm:
ISpecialPage.cs:
public interface ISpecialPage
{
string Title { get; set; }
string MetaDescription { get; set; }
string MetaKeywords { get; set; }
}
MyPage.aspx:
public partial class MyPage : System.Web.UI.Page, ISpecialPage
{
public string Title { get; set; }
public string MetaDescription { get; set; }
public string MetaKeywords { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
this.Title = "Some title";
this.MetaDescription = "Some description";
this.MetaKeywords = "Some keywords";
}
}
MasterPage.master:
public partial class MasterPage : System.Web.UI.MasterPage
{
protected void Page_Load(object sender, EventArgs e)
{
if (this.Context.Handler is ISpecialPage)
{
ISpecialPage specialPage = (ISpecialPage)this.Context.Handler;
// Logic to read the properties from the ISpecialPage and apply them to the MasterPage here
}
}
}
This way you can handle all MasterPage logic in the master page code behind file, and simply use the interface on pages you need to provide certain information.
Hope this helps you!

Related

How can I add different controls dynamically to a web page using asp.net core?

I am new to .net core - have been using aspx web pages and .net framework 4.x for a number of years. I have a project where we want to display different controls (textbox, dropdown, checkbox) on the page based on values returned from a query. For example, user chooses "A" from a dropdown list and it shows 10 controls, if they choose object B it shows 8 controls, etc. Previously in .net framework, I would use a content placeholder with an ID and then find that ID and start adding controls (controls.Add(newControl)) in the placeholder. It doesn't seem that is an option with .net core. It seems like this would be a common need for various web applications, but I'm not finding many hits.
Another question is whether this can be done in the code behind or if it has to be done on the client-side. If one of the controls in the list is a dropdown, there will be a query that a subroutine will run to get the Key/Value pairs for the dropdown. To me this means it would be more effective on the server side.
I haven't really found any good examples when I do some searching. Can anyone point me to a good resource or provide me with a basic example - either client-side or server-side? Thanks!
There are many options, but I'll describe a simple one, using server side processing. As you explained in your comment, there will be 2 pages:
One that will display the select element that will be used to choose a set of controls.
The page that will be returned according to the previous choise, displaying the selected set of controls.
I assume that you know how to build the first page.
For the second page, you can leverage the ASP.NET Core MVC pattern to achieve the desired result.
You will need the three usual MVC elements:
An Action in a Controler.
A ViewModel for your Razor View.
A Razor View.
The Action does the following:
Receives the id of the selected set of control (via the Action's parameter).
Uses this id to retrieve the information about the corresponding set of controls from your repository.
Builds a ViewModel out of the received information.
Builds a View using the obtained ViewModel.
Return the builded View.
Here is some simplified example code:
In your controller, add the following method:
#!lang-cs
Public IActionResult GetProgramControlSet(int ProgramId)
{
// Here, use the id to get the data from your repository
// that will be used to build set of controls.
// Supposing you have defined a GetControls method,
// it could look like:
var SelectedControls = MyRepository.GetControls(ProgramId);
// If needed, you can build a ViewModel out of the received SelectedControls.
var SelectedControlsViewModel = new ControlSetViewModel(SelectedControls);
return View(SelectedControlsViewModel)
}
Of course, many things are missing here: error handling, etc...
Here is what the ViewModel could be:
#!lang-cs
public class ControlSetViewModel
{
public string Name { get; private set; }
public List<IControl> Controls { get; private set; }
public ControlSetViewModel(...)
{
// Whatever needs to be done to construct the ViewModel
}
}
public enum ControlKind
{
Button,
Select,
Textarea
//...
}
public interface IControl
{
ControlKind Kind { get; }
}
public class ControlButton : IControl
{
public ControlKind Kind => ControlKind.Button;
public string Label { get; set; }
public string Text { get; set; }
public string Color { get; set; }
// ... All other needed properties for the button
}
public class ControlTextarea : IControl
{
public ControlKind Kind => ControlKind.Textarea;
public string Label { get; set; }
public string PlaceholderText { get; set; }
public string RowCount { get; set; }
// ... All other needed properties for the textarea
}
public class ControlSelect : IControl
{
public ControlKind Kind => ControlKind.Select;
public string Label { get; set; }
public string PlaceholderText { get; set; }
public List<SelectOption> Options { get; set; }
// ... All other needed properties for the select
}
public class SelectOption
{
public string Text { get; set; }
public string Value { get; set; }
}
You could also use inheritance instead of interface for the control classes.
Now the view.
It is a Razor page containing something akin to
#model ControlSetViewModel
#*... some HTML ...*#
<div>
<h1>#Model.Name</h1>
#foreach(var control in Model.Controls)
{
<div>
switch(control.GetControlKind())
{
case ControlKind.TextArea:
var Textarea = (ControlTextarea)control;
<label>#Textarea.Label</label>
<textarea rows="#Textarea.RowCount"/>
break;
case ControlKind.Select:
var Select = (ControlSelect)control;
<label>#Select.Label</label>
<select>
#foreach(var option in Select.Options)
{
<option value="#option.Value">#option.Text</option>
}
</select>
break;
#*... etc ...*#
default:
#*... etc ...*#
}
</div>
}
</div>
#*... More HTML ...*#
Of course this is far to be finished. All the infrastructure and code that will actually react to the displayed controls is missing.
Is it a form you that will be posted?
Is it Javascript code that will react to the control manipulation?
Or another mecanism?
This questions will need to be addressed.

Can't access variable from code behind

I'm trying to write variables from my code behind but am being told "The name 'X' does not exist in the current context."
When I search for this error, I see that they should be declared public or protected at the class level, and I have done that.
I try writing the variable in default.aspx using:
<%=metaRedirect%>
or
<%#metaRedirect%>
My default.aspx.cs is as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace go
{
public partial class _default : System.Web.UI.Page
{
protected string message1 = "";
protected string url = "";
protected string shortCode = "";
protected string jsRedirect = "";
protected string metaRedirect = "";
protected void Page_Load(object sender, EventArgs e) {
//do stuff
}
}
Edit: I have also tried declaring them like this:
public partial class _default : System.Web.UI.Page
{
protected string message1 { get; set; }
protected string url { get; set; }
protected string shortCode { get; set; }
protected string jsRedirect { get; set; }
protected string metaRedirect { get; set; }
With no difference.
Edit 2: Is it possible I am missing something from the first line of the .aspx file? I have:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" %>
Sounds like your page isn't properly hooked up to the class. Make sure you've got Inherits="go._default" in your page header in your aspx page.
EDIT After your edit, this is definitely your problem.
Also, you can definitely use fields if you want instead of properties (but I always recommend properties), and you can use protected instead of public. Things shouldn't be public unless you really do want to access them from outside of this class or inheritance chain.
As you are using binding expression you need to call Page.DataBind()
in the page_load and make the variable Public
public string metaRedirect { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
metaRedirect="Hello World.";
Page.DataBind();
}
And try:
<%#metaRedirect%>
or you can just Simply call it like
<%= metaRedirect%>

Access Master Page public method from user control/class/page

I am to access a method on my master page. I have an error label which I want to update based on error messages I get from my site.
public string ErrorText
{
get { return this.infoLabel.Text; }
set { this.infoLabel.Text = value; }
}
How can I access this from my user control or classes that I set up?
To access the masterpage:
this.Page.Master
then you might need to cast to the actual type of the master page so that you could get the ErrorText property or make your master page implement an interface containing this property.
Page should contain next markup:
<%# MasterType VirtualPath="~/Site.master" %>
then Page.Master will have not a type of MasterPage but your master page's type, i.e.:
public partial class MySiteMaster : MasterPage
{
public string ErrorText { get; set; }
}
Page code-behind:
this.Master.ErrorText = ...;
Another way:
public interface IMyMasterPage
{
string ErrorText { get; set; }
}
(put it to App_Code or better - into class library)
public partial class MySiteMaster : MasterPage, IMyMasterPage { }
Usage:
((IMyMasterPage )this.Page.Master).ErrorText = ...;

Calling child constructor by casting (ChildClass)parentObject; to track revisions

To track revisions of a Page class, I have a PageRevision class which inherits from Page and adds a revision ID (Guid RevisionID;).
If possible, how should I cast an existing Page object to a PageRevision and ensure that the PageRevision constructor is called to create a new revision ID?
I could could have a PageRevision(Page page) constructor which generates the Guid and copies all the Page attributes, but I want to automate it, especially if a Page class has many attributes (and I later add one, and forget to modify the copy constructor).
Desired use
Page page = new Page(123, "Page Title", "Page Body"); // where 123 is page ID
PageRevision revision = (PageRevision)page;
// now revision.RevisionID should be a new Guid.
Page, PageRevision classes:
public class Page
{
public int ID { get; set; }
public string Title { get; set; }
public string Body { get; set; }
}
public class PageRevision : Page
{
public Guid RevisionID { get; set; }
public PageRevision()
{
this.RevisionID = Guid.NewGuid();
}
}
Edit based on feedback:
Besides the now-obvious (Horse)Animal; casting problem, Jon Skeet recommends a composite revision:
public class PageRevision : Page
{
private readonly Page page;
private readonly Guid id;
public Guid RevisionID { get { return id; } }
public Page Page { get { return page; } }
public PageRevision(Page page)
{
this.id = Guid.NewGuid();
this.page = page;
}
}
However, this is quite different from my data model and I'd like to keep the two as similar as possible. In my database, the PageRevisions table has the same columns as the Pages table, expect for an extra RevisionID column. This is easy to version with a database trigger.
In the light of this composite approach, would it make more sense to have a PageRevisions to store all page data: a RevisionID, Title and Body, while a Pages table only stores an URL Slug and a RevisionID that refers to the PageRevisions table?
Why not make your PageRevision class compose instead of inheriting?
public class PageRevision : Page
{
private readonly Page page;
private readonly Guid id;
public Guid RevisionID { get { return id; } }
public Page Page { get { return page; } }
public PageRevision(Page page)
{
this.id = Guid.NewGuid();
this.page = page;
}
}
You cannot.
A horse is an animal, but not every animal is a horse.
So horse => animal is possible, but animal => horse not. And you are trying to cast your animal into a horse.
The PageRevision constructor ALWAYS gets called regardless if you cast the class to PageRevision or not. So this isn't going to work at all.
It likely makes more sense you tell why you want to do that because you are likely doing that for reasons that are solved in other ways.
During a cast, there is no constructor called, because the object is already created.
Although your cast will fail at runtime, cause Page cannot be cast to PageRevision (the other way is possible)
In your case i would add the RevisionId to your base class Page. If you create a Page object it could be created with Guid.Empty. Derived classes could set the RevisionId using an constructor of your base class Page.
public class Page {
public Page() {
RevisionId = Guid.Empty;
}
protected Page(Guid revisionId) {
RevisionId = revisionId;
}
public Guid RevisionId {
get;
private set;
}
}
public class PageRevision : Page {
public PageRevision()
: base(Guid.NewGuid()) {
}
}

How do I reference an ASP.net MasterPage from App_Code

I'm working on a .net 3.5 site, standard website project.
I've written a custom page class in the sites App_Code folder (MyPage).
I also have a master page with a property.
public partial class MyMaster : System.Web.UI.MasterPage
{
...
private string pageID = "";
public string PageID
{
get { return pageID; }
set { pageID = value; }
}
}
I'm trying to reference this property from a property in MyPage.
public class MyPage : System.Web.UI.Page
{
...
public string PageID
{
set
{
((MyMaster)Master).PageID = value;
}
get
{
return ((MyMaster)Master).PageID;
}
}
}
I end up with "The type or namespace name 'MyMaster' could not be found. I've got it working by using FindControl() instead of a property on the MyMaster page, but IDs in the master page could change.
I've tended to do the following with Web Site projects:
In App_Code create the the following:
BaseMaster.cs
using System.Web.UI;
public class BaseMaster : MasterPage
{
public string MyString { get; set; }
}
BasePage.cs:
using System;
using System.Web.UI;
public class BasePage : Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (null != Master && Master is BaseMaster)
{
((BaseMaster)Master).MyString = "Some value";
}
}
}
My Master pages then inherit from BaseMaster:
using System;
public partial class Masters_MyMasterPage : BaseMaster
{
protected void Page_Load(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(MyString))
{
// Do something.
}
}
}
And my pages inherit from BasePage:
public partial class _Default : BasePage
I found some background to this, it happens because of the way things are built and referenced.
Everything in App_Code compiles into an assembly.
The rest, aspx files, code behind, masterpages etc, compile into another assemlby that references the App_Code one.
Hence the one way street.
And also why Ben's solution works. Thanks Ben.
Tis all clear to me now.
I realise there are already accepted solutions for this, but I just stumbled across this thread.
The simplest solution is the one listed in the Microsoft website
(http://msdn.microsoft.com/en-us/library/c8y19k6h.ASPX )
Basically it says, your code will work as-is, if you include an extra directive in the child page aspx:
<%# MasterType VirtualPath="~/MyMaster.Master" %>
Then you can directly reference the property in the base MyPage by:
public string PageID
{
set
{
Master.PageID = value;
}
get
{
return Master.PageID;
}
}

Categories

Resources