ASP.NET: User control with access to the controls that it wraps - asp.net

I have a bunch of occurrences of this kind of boilerplate code in my ASP.NET project.
<div class="inputfield">
<div class="tl">
<span class="tr"><!-- --></span>
<span class="ll"><!-- --></span>
<div class="lr">
<div class="cntnt">
<asp:TextBox .../>
</div>
</div>
</div>
</div>
As you may have guessed, everything in that snippet is pure boilerplate except for the innermost text field.
What is the best way to avoid such boilerplate in ASP.NET? In e.g. Django I would make a custom tag for it, as such:
{% boiler %}
<input ... />
{% endboiler %}
I was thinking that maybe I can create a user control, but all the tutorials on ASP.NET user controls that I've found are very simplistic and "self-closing", i.e. they are not aware of the contents of the tag. I need something along the lines of:
<Hello:MyControl>
<asp:TextBox .../>
</Hello>
So my question is the following: what's the best way to avoid the boilerplate?

You can use an ITemplate property. Thus, you can inject different content in different situations.
[PersistChildren(false), ParseChildren(true, "ContentTemplate")]
public partial class WebUserControl1 : System.Web.UI.UserControl
{
[System.ComponentModel.Browsable(false), System.Web.UI.PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate ContentTemplate { get; set; }
protected override void CreateChildControls()
{
if (this.ContentTemplate != null)
this.ContentTemplate.InstantiateIn(this);
base.CreateChildControls();
}
}

Put the asp:TextBox in your user control, along with the other html tags. Provide properties on your user control that match the properties of the text box, so that you would do something like this:
<Hello:MyControl ID="myControl" runat="server" Width="300px" MaxLength="30" />
and then the width and maxlength properties would just get transferred to the internal textbox.
You could also provide access to the textbox from the usercontrol and set all the properties in the code behind.

Create a class like this:
[PersistChildren(false), ParseChildren(true, "ContentTemplate")]
public class CustomContent:WebControl
{
[System.ComponentModel.Browsable(false), System.Web.UI.PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public ITemplate ContentTemplate { get; set; }
private PlaceHolder m_placeHolder;
protected override void CreateChildControls()
{
m_placeHolder = new PlaceHolder();
if (this.ContentTemplate != null)
this.ContentTemplate.InstantiateIn(m_placeHolder);
Controls.Add(m_placeHolder);
}
protected override void RenderContents(HtmlTextWriter writer)
{
writer.Write(#"<div class=""inputfield"">
<div class=""tl"">
<span class=""tr""><!-- --></span>
<span class=""ll""><!-- --></span>
<div class=""lr"">
<div class=""cntnt"">
");
base.RenderContents(writer);
writer.Write(#" </div>
</div>
</div>
</div>
");
}
}
This class isn't a "User Control" it's a "Server Control". You can do the same thing with a user control but you'll have issues with the designer. This will work in the designer.
And you can put markup like this in your ASPX:
<uc1:CustomContent runat="server" ID="content">
<asp:textbox runat="server"></asp:textbox>
</uc1:CustomContent>
don't forget the Register page declaration at the top of the aspx
<%# Register tagprefix="uc1" Assembly="Assembly where CustomContent is" Namespace="namespace where CustomContent is" %>
You can put whatever you want inside the uc1:CustomContent tags and it will render that boilerplate html around it. If you are curious about how ITemplate works, there are plenty of articles on msdn, etc.

Related

Display item of instance

I have made an instance of the type competition by retrieving data from my database.
The function selectCompetitionById return one row of data.
Competition competition = BLLc.selectCompetitionById(competitionId);
How can I display item of this instance on my page? (Repeater doesn't work)
If you want to create an auto-generated form, then you could use a DetailsView:
<asp:DetailsView ID="competitionDetails" RunAt="Server" AutoGenerateColumns="true" />
Create a dummy array (with just the one item) as the data source:
competitionDetails.DataSource = new Competition[] { competition };
The other way is to use standard HTML template that references your object. Make it a property of the page first:
<script runat="Server">
public Competition competition { get; set; }
void Page_Load()
{
competition = BLLc.selectCompetitionById(competitionId);;
this.DataBind();
}
</script>
Then reference it in markup as needed:
<div>
<span>Title</span>
</div>
<div>
<span><%# competition.Title %></span>
</div>
<div> etc ... </div>

Model Binding binding at more than one level

I have several forms, all which require a checkbox for each province/state. Therefore, I've made a partial view to render the checkboxes inside a form to promote code re-use. But when the user submits the form to a controller method, the RegionsViewModel does not get binded. The overall question is, how can I get multiple forms to share a partial view and view model?
Here's a sample code of my situation
Models
public class Form1ViewModel
{
/* Some properties */
public RegionsViewModel Regions {set; get;}
}
public class Form2ViewModel
{
/* Some properties */
public RegionsViewModel Regions {set; get;}
}
public class Form3ViewModel
{
/* Some properties */
public RegionsViewModel Regions {set; get;}
}
public class RegionsViewModel
{
public bool ON {set; get;}
public bool QC {set; get;}
/* this continues for all provinces and states */
}
Controller
[HttpPost]
public ActionResult Form(Form1VewModel model) {
//All properties except for model.RegionViewModel does not bind properly to the submitted form :(
}
Form1ViewModel.aspx
<% using (Html.BeginForm())
{%>
<!-- Binds some property -->
<% Html.RenderPartial("Controls/RegionSelector", Model.Regions); %>
<input type="submit" value="Submit Form!" />
<%}%>
Controls/RegionSelector.ascx
<%=Html.CheckBoxFor(x => x.AvailableProvince_ON> ON
<%=Html.CheckBoxFor(x => x.AvailableProvince_QC> QC
<!-- Binds to all provinces and states -->
Update
Replaced "Model.RegionSelectorVm" with "Model.Region". Thanks for finding the bug in my demo code Darin Dimitrov.
What is RegionSelectorVm? It seems that this is the type of your partial. Try with editor templates. It's cleaner:
<% using (Html.BeginForm()) { %>
<!-- Binds some property -->
<%= Html.EditorFor(x => x.Regions) %>
<input type="submit" value="Submit Form!" />
<% } %>
and inside ~/Views/Shared/EditorTemplates/RegionsViewModel.ascx:
<%# Control
Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<AppName.Models.RegionsViewModel>" %>
<%= Html.CheckBoxFor(x => x.ON) %> ON
<%= Html.CheckBoxFor(x => x.QC) %> QC
<!--
Continue with inputs for the provinces and states
which are part of the RegionsViewModel model
-->
Now everything should bind correctly.

Custom ViewModel with MVC 2 Strongly Typed HTML Helpers return null object on Create?

I am having a trouble while trying to create an entity with a custom view modeled create form. Below is my custom view model for Category Creation form.
public class CategoryFormViewModel
{
public CategoryFormViewModel(Category category, string actionTitle)
{
Category = category;
ActionTitle = actionTitle;
}
public Category Category { get; private set; }
public string ActionTitle { get; private set; }
}
and this is my user control where the UI is
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<CategoryFormViewModel>" %>
<h2>
<span><%= Html.Encode(Model.ActionTitle) %></span>
</h2>
<%=Html.ValidationSummary() %>
<% using (Html.BeginForm()) {%>
<p>
<span class="bold block">Başlık:</span>
<%=Html.TextBoxFor(model => Model.Category.Title, new { #class = "width80 txt-base" })%>
</p>
<p>
<span class="bold block">Sıra Numarası:</span>
<%=Html.TextBoxFor(model => Model.Category.OrderNo, new { #class = "width10 txt-base" })%>
</p>
<p>
<input type="submit" class="btn-admin cursorPointer" value="Save" />
</p>
<% } %>
When i click on save button, it doesnt bind the category for me because of i am using custom view model and strongly typed html helpers like that
<%=Html.TextBoxFor(model => Model.Category.OrderNo) %>
My html source looks like this
<form action="/Admin/Categories/Create" method="post">
<p>
<span class="bold block">Başlık:</span>
<input class="width80 txt-base" id="Category_Title" name="Category.Title" type="text" value="" />
</p>
<p>
<span class="bold block">Sıra Numarası:</span>
<input class="width10 txt-base" id="Category_OrderNo" name="Category.OrderNo" type="text" value="" />
</p>
<p>
<input type="submit" class="btn-admin cursorPointer" value="Kaydet" />
</p>
</form>
How can i fix this?
Your View Model needs a default constructor without parameters and you need public set methods for each of the properties. The default model binder uses the public setters to populate the object.
The default model binder has some rules it follows. It chooses what data to bind to in the following order:
Form parameters from a post
Url route data defined by your route definitions in global.asax.cs
Query string parameters
The default model binder then uses several strategies to bind to models/parameters in your action methods:
Exact name matches
Matches with prefix.name where prefix is the parent class and name is the subclass/property
Name without prefix (as long as there are no collisions you don't have to worry about providing the prefix)
You can override the behavior with several options from the Bind attribute. These include:
[Bind(Prefix = "someprefix")] -- Forces a map to a specific parent class identified by the prefix.
[Bind(Include = "val1, val2")] -- Whitelist of names to bind to
[Bind(Exclude = "val1, val2")] -- Names to exclude from default behavior
You could use editor templates. Put your ascx control in ~/Views/Shared/EditorTemplates/SomeControl.ascx. Then inside your main View (aspx page) include the template like so (assuming your view is strongly typed to CategoryFormViewModel):
<%= Html.EditorForModel("SomeControl") %>
instead of
<% Html.RenderPartial("SomeControl", Model) %>
Make a default constructor for your viewmodel and initialize the Category there
public CategoryFormViewModel()
{
Category = new Category()
}
And at your controller action receive the viewmodel
public ActionResult ActionName(CategoryFormViewModel model)
{
//here you can access model.Category.Title
}

Formatting Controls in ASP.NET

I feel as though this this is a simple question, but can't find an answer anywhere. We've got an interface we're trying to move to an ASP.NET control. It currently looks like:
<link rel=""stylesheet"" type=""text/css"" href=""/Layout/CaptchaLayout.css"" />
<script type=""text/javascript"" src=""../../Scripts/vcaptcha_control.js""></script>
<div id="captcha_background">
<div id="captcha_loading_area">
<img id="captcha" src="#" alt="" />
</div>
<div id="vcaptcha_entry_container">
<input id="captcha_answer" type="text"/>
<input id="captcha_challenge" type="hidden"/>
<input id="captcha_publickey" type="hidden"/>
<input id="captcha_host" type="hidden"/>
</div>
<div id="captcha_logo_container"></div>
</div>
However all the examples I see of ASP.NET controls that allow for basical functionality - i.e.
public class MyControl : Panel
{
public MyControl()
{
}
protected override void OnInit(EventArgs e)
{
ScriptManager.RegisterScript( ... Google script, CSS, etc. ... );
TextBox txt = new TextBox();
txt.ID = "text1";
this.Controls.Add(txt);
CustomValidator vld = new CustomValidator();
vld.ControlToValidate = "text1";
vld.ID = "validator1";
this.Controls.Add(vld);
}
}
Don't allow for the detailed layout that we need. Any suggestions on how I can combine layout and functionality and still have a single ASP control we can drop in to pages? The ultimate goal is for users of the control to just drop in:
<captcha:CaptchaControl ID="CaptchaControl1"
runat="server"
Server="http://localhost:51947/"
/>
and see the working control.
Sorry for the basic nature of this one, any help is greatly appreciated.
Although you may want to look into user controls, the following page has an example of doing this using a web control. http://msdn.microsoft.com/en-us/library/3257x3ea.aspx The Render() method does the output of the actual HTML for the control.
There are a couple of ways to do it. You can make a custom control, or a user control. I think you will find it easier to do a user control. It lets you lay out parts of your control as you would a regular page. Here is some example documentation: http://msdn.microsoft.com/en-us/library/26db8ysc(VS.85).aspx
By contrast a custom control typically does all of the rendering in code (as your example you show). It is harder to make your first control in this way.

Share variable across site ASP.NET

I have a class isSearching with a single boolean property in a 'functions' file in my webapp. On my search page, I have a variable oSearchHandler declared as a Public Shared variable. How can I access the contents of oSearchHandler on other pages in my webapp?
Code with Session....
'search.aspx
Public Function oSearchString(ByVal oTextBoxName As String) As String
For Each oKey As String In Request.Form.AllKeys
If oKey.Contains(oTextBoxName) Then
Session.Add("searching", True)
Session.Add("search-term", Request.Form(oKey))
Return Request.Form(oKey)
End If
Next
Return ""
End Function
'theMaster.master
<%
If Session("searching") Then
%><ul style="float: right;">
<li>
<div class="gsSearch">
<asp:TextBox ID="searchbox" runat="server"></asp:TextBox>
</div>
</li>
<li>
<div class="gsSearch">
<asp:Button ID="searchbutton" runat="server" Text="search" UseSubmitBehavior="true" PostBackUrl="search.aspx" CssClass="searchBtn" />
</div>
</li>
</ul>
<%
End If
%>
I think that the session will work just fine.
If you're talking about accessing these variables between page interactions, you need to bear in mind that the state is discarded between pages.
Instead, you need to store this data in Session State.
If it's not across page interactions, but simply accessing the data from other parts of the code, the key is that the ASP.NET page becomes a class and your public shared variable, a static property of that class.
So, you'd access it from elsewhere using PageName.oSearchHandler
[EDIT] Can you give us some more information about what oSearchHandler is and how you're intending using it? We can probably offer a more considered recommendation, then.
If you want it accessible from multiple pages you should pull it off that individual page class and put it in a more globally accessable place such as the Application collection. Given the naming of the variable, is 0SearchHandler a delegate? I'm not as familiar with VB.NET as much or the terminology.
Update: Steve Morgan mentioned using the Session collection, when "static" or "shared" was mentioned, i was thinking more globally. Depending on how your using the variable you can use the "Application" if it will be shared between users and sessions, or the "session" if it will be used by one user in one session. In VB.NET they are both easy to use:
Session("yourKey") = YourObjectYouWantToSave
Application("yourKey") = YourObjectYouWantToSave
Very simple stuff.
'search.aspx
Public Function oSearchString(ByVal oTextBoxName As String) As String
For Each oKey As String In Request.Form.AllKeys
If oKey.Contains(oTextBoxName) Then
Session("searching") = True
Session("search-term") = Request.Form(oKey)
Return Request.Form(oKey)
End If
Next
Return ""
End Function
' theMaster.master.vb
In PageLoad Method:
...
Dim bSearching as Boolean
bSearching = IIf(Session("searching") is Nothing, False, Session("searching") )
ulSearch.visible = bSearching
...
'theMaster.master
<ul style="float: right;" runat="server" id="ulSearch">
<li>
<div class="gsSearch">
<asp:TextBox ID="searchbox" runat="server"></asp:TextBox>
</div>
</li>
<li>
<div class="gsSearch">
<asp:Button ID="searchbutton" runat="server" Text="search" UseSubmitBehavior="true" PostBackUrl="search.aspx" CssClass="searchBtn" />
</div>
</li>
</ul>
Ok, that is some extra code but I think you would have less problems with it. Plus my VB is a bit rusty. Actually, If the master page is the page you will be using it on, I would put the variable as a public property on that masterpage. You can access the pages master page with this.Master (at least in C#, I think it's Me.Master in VB.NET).

Resources