I am attempting to allow my web designers to use the metadata we have about database fields in the asp.net pages they are creating. The most obvious one is as follows:
<asp:TextBox runat="server" id="txTextBox" MaxLength="<Value From Metadata here>" ... />
All the required metadata is stored in our class objects and is accessible due to its public static nature.
The benefit of this would be that they can set values which
a) might change without them being aware or caring
b) improve the user experience with very little coding effort
and all without having them need worry about what the value is or where it has come from. This will primarily be used for automatically bound controls - i.e. ones which are added with little or no developer interaction.
This question is very similar to One of my previous questions which now works wonderfully (but that was in WPF / XAML ).
The main point of this question is that I want as little developer requirement on this as possible - Ideally there would be some <%# Constant.Value %> type syntax which could be used directly within the Maxlength="" attribute of the asp:Textbox control meaning that no code needs to be added to a page/usercontrol at all.
I have a feeling it isn't possible, but I would love someone to prove me wrong.
Ta
You can use a data binding expression:
<asp:TextBox MaxLength="<%# Constant.Value %>" />
but, that requires it to be in a databound control. If it's not in a repeater or somesuch, you'll need to call Container.DataBind() at some point in the page lifecycle.
Alternatively, you could create an ExpressionBuilder which would allow syntax such as:
<asp:TextBox MaxLength="<%$ Constants:Value %>" />
Here's a sample that'll pull from a single static dictionary:
using System;
using System.Web.UI;
using System.Web.Compilation;
using System.CodeDom;
using System.Collections.Generic;
class ConstantsExpressionBuilder : ExpressionBuilder {
private static readonly Dictionary<string, object> Values =
new Dictionary<string, object>() {
{ "Value1", 12 },
{ "Value2", false },
{ "Value3", "this is a test" }
};
public override bool SupportsEvaluate { get { return true; } }
public override object EvaluateExpression(object target, BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context) {
string key = entry.Expression.Trim();
return GetValue(key);
}
public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context) {
CodePrimitiveExpression keyExpression = new CodePrimitiveExpression(entry.Expression.Trim());
return new CodeMethodInvokeExpression(this.GetType(), "GetValue", new CodeExpression[] { keyExpression });
}
public static object GetValue(string key) {
return Values[key];
}
}
You'd register this in web.config:
<system.web>
<compilation>
<expressionBuilders>
<add expressionPrefix="Constants" type="ConstantsExpressionBuilder" />
</expressionBuilders>
</compilation>
</system.web>
And call it in an ASPX page:
<asp:Textbox runat="server" MaxLength="<%$ Constants:Value1 %>" ReadOnly="<%$ Constants:Value2 %>" Text="<%$ Constants:Value3 %>" />
Which should produce:
<input type="text" maxlength="12" readonly="false" value="this is a test" />
in the HTML output.
I think you should be able to do it with something like this
<asp:TextBox runat="server" id="txTextBox" MaxLength="<%=Constants.SomeValue%>" />
But my only concern is that this doesn't really make much sense. If the constant is stored in a .cs file in order to make a change that would cause the new constant value to be reflected in the UI you would have to recompile the site. I think it may be easier just to have a hard coded value in the .aspx page that can be changed easily without the need to recompile the entire codebase. Maybe I'm not understanding the problem though.
Related
I'm using ASP.Net for web development. I've situation here. say I've many HTML/ASP.Net controls on my page, and I've to set visibility of each control based on Logged-in user's role. for this I'm doing as follows:
Storing each Function-Name and Role combination is DB
During render, checking for role permission for each HTML/ASP.Net control.
e.g: Displaying something like this:
<asp:Label runat="server" id="Lbl1" visible='<%=CheckVisibility("Display","Admin")%>'>
public bool CheckVisibility(string FunctionName, string RoleName){
// checks for db entry and returns bool value
}
Problem is, I've to do this for all the Controls. Is there any other optimized approach available to do this? please help me
Given only your description of what you're trying to solve, I would suggest you create your own controls which inherit from the built-in ones and add some properties to them. Then the controls can do the visibility check themselves. Something like
namespace StackOverflowTest
{
public class Label : System.Web.UI.WebControls.Label
{
public string DisplayRoles { get; set; }
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
Visible = PermissionHelper.CheckPermission("Display", DisplayRoles.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries));
}
}
public static class PermissionHelper
{
public static bool CheckPermission(string function, string[] allowedRoles)
{
// TODO: hit some sort of cache, so you don't make a bajillion queries to the DB
return true;
}
}
}
And then if you put this in your web.config (sorry, can't figure out how to do proper XML formatting here): <system.web>
<pages>
<controls>
<add tagPrefix="sot" assembly="StackOverflowTest" namespace="StackOverflowTest"/>
</controls>
</pages>
</system.web>
You can add this to your markup:
<sot:Label ID="AdminLabel" runat="server" Text="Bind this in OnLoad/!IsPostback or something" DisplayRoles="Admin,Management" />
This is just one of the many ways you can do this. It all depends on the requirements, of course. But I'm pretty sure you'll need your own classes for controls to have the possibility of making it manageable.
C# code:
public enum previlageType
{
superAdminPrevilages=1,
partnerPrevilages = 2,
dealerPrevilages = 3,
customerPrevilages=4
}
if ((previlageType)Enum.Parse(typeof(previlageType), Session["previlageType"].ToString())== previlageType.partnerPrevilages)
{
accordion.Visible = false;
}
ASP code:
<div id="accordion" runat="server">
Hello World, I'l be Hidden if a person with partner previlages is logged in. BUt will be visible if superadmin or any person except Partner logs in.
</div>
Hope it helps
If you're using a master page, you could get all the child page controls on load (get contentplaceholders, then get contentplaceholder controls).
Then when you have all the control names, do a lookup on your table and set its visibility to false if needed.
(below is vb but the translation should be easy.)
For Each cp As String In Me.ContentPlaceHolders
For Each ctl As Control In Me.FindControl(cp).Controls
If adminonly.Contains(ctl.ID) Then
ctl.Visible = False
End If
Next
Next
I have created a class doing some jobs like GridView inherit from System.Web.UI.WebControls.WebControl.
public class IHGridView : System.Web.UI.WebControls.WebControl
{
// inside here, actually return Repeater class.
protected override void OnInit(EventArgs e)
{
_repeater.ItemTemplate = new IHGridItemTemplate(ListItemType.Item, this.Columns);
this.Controls.Add(_repeater);
}
}
I also created ItemTemplate for my repeater in IHGridView.
public class IHGridItemTemplate : ITemplate
{
}
IHGridView class returns Repeater and some html codes, but in convenience to deveop I have created some stuff.
public class Columns : StateManagedCollection
{
}
public class IHBoundFieldBase : IStateManager
{
}
public class IHLabelField : IHBoundFieldBase
{
}
Now in my aspx, I can use this like below:
<cc1:IHGridView ID="IHGridView1" runat="server" EditMode="View">
<Columns>
<cc1:IHLabelField ID="IHLabelField7" DataField="PERSON_NAME" HeaderText="PersonName" />
</Columns>
</cc1:IHGridView>
Now I come up with a problem.
I cannot use DataBinder.Eval in aspx.
<cc1:IHLabelField ID="IHLabelField7" HeaderText="PersonName" Text='<%# DataBinder.Eval(Container.DataItem, "PERSON_NAME") %>' />
This gives me an error.
The error message is below: CS1061: There is no definition of 'DataItem' in 'System.Web.UI.Control'. There is no extendable method 'DataItem' in 'System.Web.UI.Control''s first argument. Please check if there is using rubric or assembly reference. This was written in Korean, but I translated into English.
Could anyone give me a clue to solve this problem?
In templated controls, the template is instantiated in the container. For data-binding to work in the templated fields, its recommended that container should implement IDataItemContainer interface - the interface implementation should be supplying the data-item.
AFAIK, to support data binding expressions, ASP.NET parser injects handler for DataBinding event for the control (whose properties uses these expressions) and then in the handler, it generates code that looks for data-item in the container.
So in your example, if you wish to use data-binding expression in the IHLabelField.Text property then the control's naming container should either implement IDataItemContainer or should have DataItem property. So in this case, you will probably need DataItem on IHGridView control - and it wouldn't work the way you want.
here is an example we used. i hope it helps
<asp:HyperLink ID="phoneManagementHyperLink" runat="server" Text='<%# (Container.DataItem as WcfUser).firstName + " " + (Container.DataItem as WcfUser).lastName%>'
I might be completely off track by now, so I will just ask this here so someone can help me.
What I want to do, is to insert a value from my web.config, stored in an applicationSettings area, into my aspx markup. Specifically I want to reade a URL from config. This is the configSection setup I use
<configSections>
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=123456">
<section name="MyApp.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=12345" requirePermission="false" />
</configSections>
Later in that file are the actual settings like so:
<applicationSettings>
<MyApp.Properties.Settings>
<setting name="ImagesUrl" serializeAs="String">
<value>http://resources/images/</value>
</setting>
Now I want to reference the above value in markup like this:
<asp:Image ID="Image1" runat="server" ImageUrl="<%$AppSettings:ImagesUrl%>/Image1.jpg
I know there's an expression available <%$ AppSettings: ImagesUrl %>, but I'm not using the appsettings part of web.config, rather the configSection.
EDIT:
I believe I can only do it with ExpressionBuilder, because I have to concatenate the string with the individual image name. I changed the example above to reflect that.
I like Bert Smith Code Solution below for accessing the config section, only I need to put it in an expression builder.
I'm stuck at overriding the GetCodeExpression method from where I would call the Configuration Manager, but I don't understand how to build an expression the parameters.
public class SettingsExpressionBuilder: ExpressionBuilder
{
public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
{
return ??
}
EDIT
The result looks like this, and works for all kinds of files, not just images:
<asp:ScriptReference Path='<%$Code:GetAppSetting("ResourcesUrl","JS/jquery/jquery.jqplot.js")%>'
and I simply used the example from Microsoft to return any kind of code from the expression builder:
return new CodeSnippetExpression(entry.Expression);
And GetAppSetting is a method in my custom Page class.
Typically you would create a custom settings class to read these values out as this artical describes. Personally, I would just use the appSettings as suggested above as this is existing functionality and for what your doing would on the surface seem adequate.
However, not knowing your circumstances, what your attempting to do could be solved without the custom settings like so:
In the code behind I created a protected function to retrieve the setting
protected string GetCustomSetting(string Section, string Setting)
{
var config = ConfigurationManager.GetSection(Section);
if (config != null)
return ((ClientSettingsSection)config).Settings.Get(Setting).Value.ValueXml.InnerText;
return string.Empty;
}
Then in the aspx markup I call this function
<div>
<label runat="server" id="label"><%=GetCustomSetting("applicationSettings/MyApp.Properties.Settings", "ImagesUrl") %></label>
</div>
Hope this helps.
Follow Up:
The CodeExpression will look something like this:
public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
{
var config = ConfigurationManager.GetSection("applicationSettings/MyApp.Properties.Settings");
return new CodePrimitiveExpression(((ClientSettingsSection)config).Settings.Get(entry.Expression).Value.ValueXml.InnerText);
}
In my Test, I created a class called CustomSettingsExpressionBuilder and added it to the App_Code folder. Added the configuration for the custom express to the web.config and called it from aspx like so:
<asp:Label ID="Label1" runat="server" Text="<%$CustomSettings:ImagesUrl %>"></asp:Label>
Does it has to be in markup? Why don't you set it in code-behind.
Image1.ImageUrl= //fetch your settings here.
One another way would be defining a property or static method in your code-behind and then using that in the markup.
I'm not sure about the ASP.NET bit of it, but if this was normal code you'd do MyApp.Properties.Settings.Default.ImagesUrl, so try
<asp:Image ID="Image1" runat="server"
ImageUrl="<%$MyApp.Properties.Settings.Default.ImagesUrl%>
It would definitely be easier to store this in <appSettings> though.
I'm trying to show a custom column in my gridview which displays a content type based on a couple of boolean fields in my database. Everything works fine but it's causing a lot of overhead the way I do it now.. like this:
<ItemTemplate>
<asp:Label ID="lblType" runat="server" Text='<%# GetType((int)DataBinder.Eval(Container.DataItem))%>' />
</ItemTemplate>
This calls a function GetType which queries the database based on the ArticleID. Of course this happens for every item in the gridview. Now I would like to know if it's possible to send the current (subsonic) collection item to this function instead? Because the item is already available but I don't know how to put this in my itemtemplate.
My current item is DAL.Article which contains everything I need.
I hope I made myself clear a little !Thanks for your time.
Kind regards,
Mark
Subsonic generated classes are partial and thus extendable.
Let's say you have a DAL object called Person. You can create a new file Person.cs (in a different folder of course).
namespace Your.Dal.Namespace {
public partial class Person
{
public string DisplayName
{
get
{
return String.Format("{0}, {1}", this.LastName, this.FirstName);
}
}
}
}
Now you can access the DisplayName property of your class:
PersonCollection col = new PersonCollection().Load();
foreach(Person p in col)
Console.WriteLine(p.DisplayName);
I use this technique for binding Subsonic Collections to a Windows.Forms DataGridView a lot.
But it should work for asp.net, too.
Im using intelligencia urlrewriter as my url rewrite module. I have one very strange problem which only occurs when an url is rewritten but to make it more fun, not on all rewritten pages.
Edit: Forgot to tell you what's the problem boing boing. the problem is that my Page_Load event gets fired 2 times.
This is how my form rewrite adapter looks like:
using System;
using System.Web.UI;
using System.Web;
using System.Web.UI.WebControls;
public class FormRewriterControlAdapter : System.Web.UI.Adapters.ControlAdapter
{
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
base.Render(new RewriteFormHtmlTextWriter(writer));
}
}
public class RewriteFormHtmlTextWriter : HtmlTextWriter
{
public RewriteFormHtmlTextWriter(HtmlTextWriter writer)
: base(writer)
{
this.InnerWriter = writer.InnerWriter;
}
public RewriteFormHtmlTextWriter(System.IO.TextWriter writer)
: base(writer)
{
base.InnerWriter = writer;
}
public override void WriteAttribute(string name, string value, bool fEncode)
{
// If the attribute we are writing is the "action" attribute, and we are not on a sub-control,
// then replace the value to write with the raw URL of the request - which ensures that we'll
// preserve the PathInfo value on postback scenarios
if ((name == "action"))
{
HttpContext Context = default(HttpContext);
Context = HttpContext.Current;
if (Context.Items["ActionAlreadyWritten"] == null)
{
// Because we are using the UrlRewriting.net HttpModule, we will use the
// Request.RawUrl property within ASP.NET to retrieve the origional URL
// before it was re-written. You'll want to change the line of code below
// if you use a different URL rewriting implementation.
value = Context.Request.RawUrl;
// Indicate that we've already rewritten the <form>'s action attribute to prevent
// us from rewriting a sub-control under the <form> control
Context.Items["ActionAlreadyWritten"] = true;
}
}
base.WriteAttribute(name, value, fEncode);
}
}
And this is how my web.config looks like
<!-- Here the double page_load occurs -->
<rewrite url="~/car-parts/(\d+)/(.+)" to="~/Products.aspx?type=parts&iid=$1&cid=9" />
<rewrite url="~/car-stereo/(\d+)/(.+)" to="~/Products.aspx?type=stereo&iid=$1&cid=10" />
<!-- this is working correctly -->
<rewrite url="~/car-parts/browse-by-type/(\d+)/(.+)/(\d+)/(\d+)" to="~/Browse.aspx?cid=9&type=country&countryid=$1&p=$3&filter=$4" />
I have no idea where to look anymore, i checked my html markup since i've read that could couse this problem.
Kind regards,
Mark
when I'm using this rewrite rule in my rules,this problem has been solved:
<rewrite url="^(/.+(\.gif|\.flv|\.swf|\.png|\.jpg|\.ico|\.pdf|\.doc|\.xls|\.css|\.zip|\.rar|\.js|\.xml|\.mp3)(\?.+)?)$" to="$1" processing="stop" />
but remember to use this rule after all of your .css/.js/.jpg/... rules.
Finally I found it, it was not having anything to do with the rewrite module , this is which caused the problem:
In one of my usercontrols i've used updateprogress
<asp:UpdateProgress runat="server" AssociatedUpdatePanelID="upNewsletter" DisplayAfter="0">
<ProgressTemplate>
<asp:Image runat="server" ID="imgLoading" ImageUrl="~/Images/Template/loading.gif" />
</ProgressTemplate>
</asp:UpdateProgress>
Now thats where the problem is , in the asp:Image tag. I've replaced it with a regular img tag and now everything works fine again. Took me some time to figure this out and I hope I can save you headache.
Kind regards
I've found this post searching for multiple page_loads, but I had the problem using a dynamically created CollapsePanel using a dynamically created Image. By filling the ImageUrl to a default image, the problem was solved.
header.Controls.Add( new Image
{
ID = string.Format( "headerImage_{0}", panelId ),
EnableViewState = false,
ImageUrl = "~/Images/collapse.jpg"
} );