Using webform user control on webform in MVC Project - asp.net

I am using a server control on a single web.forms page. I have to use this control on a web.forms page since its a server control, although this is actually a MVC project. So I created a web.forms folder and put my new page in it. I then copy the example code from the signature control. I get the following error:
The base class includes the field 'ctrlSign', but its type (WebSignatureCapture.SignatureControl) is not compatible with the type of control (ASP.signaturecapture_signaturecontrol_ctlsignature_ascx).
I know the code works because if I removed the ID attribute from the server control, it no longer gives me this error and my control renders. But I need the attribute for the ID so I can perform is post event... Any ideas why?
I am using this signature control. Here's the web.forms code...
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="KahunaCentralTIDRevamp.SignatureCapture.Index" %>
<%# Reference Control="~/SignatureCapture/SignatureControl/ctlSignature.ascx" %>
<%# Register TagPrefix="uc" TagName="Signature" Src="~/SignatureCapture/SignatureControl/ctlSignature.ascx" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
<head>
<title>Signature Application Demo</title>
</head>
<body>
<form id="frmOrder" method="post" runat="server">
<div>
Please Sign Below:
</div>
<div>
<uc:Signature ID="ctrlSign" SignHeight="150" SignWidth="300" SignatureCodePath="~/SignatureCapture/SignatureControl/"
SavePath="~/SignatureCapture/" SignatureFileFormat="Gif" runat="server" />
<%-- <uc:Signature id="ctlMySignature" PenColor="Red" PenWidth="2" BackColor="Yellow" SignWidth="300" SignHeight="150"
SavePath="~/Signatures/" SignatureCodePath="~/SignatureControl/" SignatureFileFormat="Gif" Runat="server"></uc:Signature>--%>
</div>
<div>
<input type="button" value=" Re-Sign " onclick="ClearSignature();">
<asp:Button runat="server" ID="btnSave" Text=" Save " onmousedown="document.getElementById('btnSave').value = 'Wait...';"
OnClientClick="DirectSave();" OnClick="btnSave_Click" />
</div>
</form>
<script language="javascript" type="text/javascript">
// This is the method that is directly called, this will save signature
// and then call server code to do further processing. You can change
// the delay of 5 seconds as per your needs
function DirectSave() {
SaveSignature();
var date = new Date();
var curDate = null;
// delay of 5 seconds, 5000 milisecons, change as per requirement
do { curDate = new Date(); }
while (curDate - date < 5000);
return true;
}
</script>
</body>
</html>

Open the .ascx markup file of the user control. It should read something like this:
<%# Control
Language="C#"
AutoEventWireup="true"
CodeFile="ctlSignature.ascx.cs"
Inherits="WebSignatureCapture.SignatureControl.ctlSignature" %>
Modify it to:
<%# Control
Language="C#"
AutoEventWireup="true"
CodeBehind="ctlSignature.ascx.cs"
Inherits="WebSignatureCapture.SignatureControl.ctlSignature" %>
Notice CodeFile -> CodeBehind.

Someone I know had a similar problem a while back, and then they found something that they could do something in the BeginRequest which sorted his problem and allowed him to use server controls in views. I did a quick search for it, and I believe that this is what he used.
Code below:
void Application_BeginRequest(object sender, EventArgs e)
{
var form = HttpContext.Current.Request.Form;
form.GetType().GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(form, false, null);
// Refinement 1:
foreach (var key in form.AllKeys.Where(key => key.Contains("$")))
{
var value = formkey;
form.Remove(key);
var newKey = key.Substring(key.LastIndexOf("$") + 1);
form.Add(newKey, value);
}
}

Related

Why can't I use an iteration variable in a LoginView?

I am building a .NET MVC app that has a page with a list of delete buttons, one for each item in a list. The problem I'm having is that the foreach variable "item" is not visible inside the LoginView, which results in the following error:
Compiler Error Message: CS0103: The name 'item' does not exist in the current context
Below is a simplified version of the view. The error occurs at the "new {id=item.Id}" in the LoggedInTemplate - the reference to "item" in the ActionLink works fine:
<% foreach (var item in Model) { %>
<%= Html.ActionLink("Item", "Details", new { id = item.Id })%>
<asp:LoginView runat="server">
<LoggedInTemplate>
<% using( Html.BeginForm( "Delete", "Items", new {id=item.Id}, FormMethod.Post))
{ %>
<input type="submit" value="Delete" runat="server" />
<% } %>
</LoggedInTemplate>
</asp:LoginView>
<% } %>
To clarify the problem is not that the Model has not been successfully passed to the View. The Model is visible from both inside and outside the LoginView. The foreach loop as no problem in iterating through the items in the Model (which is a List). The problem is that the iteration variable "item" is not accessible from within the LoginView - though the original Model is.
Is there any way to pass "item" through to the LoginView's templates? Or is building LoginViews within a foreach loops the wrong way of doing things?
Is there a scoping rule that prevents using local variables within controls - perhaps because the control is rendered at a different time to the main page?
With ASP.NET MVC you really shouldn't use user/custom controls, so if you omit the <asp:LoginView/> and write a line of code to check if the user is authenticated, you are good to go.
Instead of your current code:
<asp:LoginView runat="server">
<LoggedInTemplate>
<div>Show this to authenticated users only</div>
</LoggedInTemplate>
</asp:LoginView>
Just use an if-statement and the value of Request.IsAuthenticated:
<% if (Request.IsAuthenticated) { %>
<div>Show this to authenticated users only</div>
<% } %>
Are you passing the Model to the view and are you also inheriting from the model within that view?
So if this is a View then in your C# code you need to return the list of items like return View(listofitems);
If this is a partial view then <% Html.RenderPartial("MyPartial", listofitems) %>
And in the view you need to
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IQueryable<ListOfItems>>" %>
If all that is in place then it should work no probs.
<% foreach (var item in Model) { %>
<%= Html.ActionLink("Item", "Details", new { id = item.Id })%>
<%= if( Request.IsAuthenticated ) {
using( Html.BeginForm( "Delete", "Items", new {id=item.Id}, FormMethod.Post))
{ %>
<input type="submit" value="Delete" runat="server" />
}
} %>
<% } %>
There is no need to use the LoginView, its not really giving you anything. Use something like the above instead.
Alternatively, you can move the decision of whether to show the delete option for the specific item into the controller, so instead of doing if( Request.IsAuthenticated ) you would do if( item.ShowDelete ) ... assuming item's type is a view model. Another option is to use an extension method for the same, item.ShowDelete(). I prefer the earlier, because there might be logic associated to deciding whether to show delete for a given item, so its better to not have it in the controller or a related logic.

ASP.NET MVC 2 editor template for value types, int

I want to create a MVC 2 editor template for a value type i.e. int , has anyone done this with the preview 1 bits?
Many thanks
Will Nick Clarke's answer work when you submit the values on postback?
In MVC2 preview 2, calling Html.Textbox("abc", Model.ToString())
will render a textbox with ".abc" appended to the name, e.g.
<input id="StartDate_abc" name="StartDate.abc" type="text" value="02 Feb 09" />
which will cause problems when you postback and attempt to UpdateModel().
I did an editor template for a DateTime, the following works for me:
/Views/Shared/EditorTemplates/DateTime.ascx:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<DateTime>" %>
<%= Html.TextBox(String.Empty, Model.ToString("dd MMM yy")) %>
or, to use jQuery's DatePicker for all your DateTimes
add a reference to jQuery and jQueryUI to either your Masterpage or to the View containing the call to EditorFor.
/Views/Shared/EditorTemplates/DateTime.ascx:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<DateTime>" %>
<%= Html.TextBox("", Model.ToString("dd MMM yy")) %>
<script type="text/javascript">
$("#<%= ViewData.ModelMetadata.PropertyName %>").datepicker({ dateFormat: 'dd M y' });
</script>
Update: ASP.NET MVC3, using the Razor syntax:
#model System.DateTime
#Html.TextBox("", Model.ToString("dd MMM yy"))
<script type="text/javascript">
$("##ViewData.ModelMetadata.PropertyName").datepicker({ dateFormat: 'dd M y' });
</script>
And to use it all you need in your View is:
#Html.EditorFor(model => model.DueDate)
-Matt
I have not tried preview 1 yet but they did what you are asking for in this channel9 video:
http://channel9.msdn.com/posts/Glucose/Hanselminutes-on-9-ASPNET-MVC-2-Preview-1-with-Phil-Haack-and-Virtual-Scott/
They do both DisplayFor and EditorFor, starts around 2 minutes.
--Edit--
For value type i.e. int I was able to get it to work in the same way.
Create a model to pass to my view:
public class HomeController : Controller
{
public ActionResult Index()
{
HomeModel model = new HomeModel();
model.message = "Welcome to ASP.NET MVC!";
model.number = 526562262;
model.Date = DateTime.Now;
return View(model);
}
}
public class HomeModel
{
public string message { get; set; }
public int number { get; set; }
public DateTime Date { get; set; }
}
Link view to the model using the new template logic:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<HomeModel>" %>
<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
<p>
<% Html.EditorFor(c => c.message); %>
</p>
<p>
<% Html.EditorFor(c => c.number); %>
</p>
<p>
<% Html.EditorFor(c => c.Date); %>
</p>
Then create a template for each of the types e.g. Int32:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
Editor For My Int32: <%= Html.TextBox("abc", Model.ToString())%>
I put this in Views\Shared\EditorTemplates\Int32.ascx
I've written a blog post about how to do this by creating reusable templates in MVC 2.
My post also explains the relationship between TemplateInfo and templates.
I have found Brad Wilson's blog to have the best examples and explanations. Part-3 of the series talks specifically about value types (String, decimal, Int32).
Enjoy!

passing parameters to .aspx page using renderpartial

in my index.aspx page i want to render another module.aspx page using renderpartial
which then render a .htm file
depanding on which parameter is passed from index.aspx (it would be number ie 1,2 etc ,so as to call different different .htm file everytime depending on the parameter)
1).
now i want Index.aspx page to render module.aspx and pass it a parameter(1,2,3,etc)
[the parameters would be passed programatically (hardcoded)]
and
2).
mudule.aspx should catch the parameter and depending on it will call .htm file
my index.aspx has
<% ViewData["TemplateId"] = 1; %>
<% Html.RenderPartial("/Views/Templates/MyModule.aspx", ViewData["TemplateId"]); %>
and module.aspx contains
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<script type="text/javascript" src="/Scripts/jquery-1.3.2.js"></script>
<script type="text/javascript" src="/Scripts/Service.js"></script>
<script type="text/javascript">
debugger;
var tid = '<%=ViewData["TemplateId"] %>';
$.get("/Templates/Select/" + tid, function(result) {
$("#datashow").html(result);
});
</script>
<div id="datashow"></div>
this is my controller which is called by $.get(....) (see code)
public ActionResult Select(int id)
{
return File("/Views/Templates/HTML_Temp" +id.ToString()+".htm" , "text/html");
}
and finally
my .htm file
<div id="divdata" class="sys-template">
<p>Event Title:<input id="title" size="150" type="text"
style="background-color:yellow;font-size:25px;width: 637px;"
readonly="readonly" value="{{title}}" />
</p>
<p>Event Description:<input type="text" id="description" value="{{ description }}"
readonly="readonly" style="width: 312px" /></p>
<p>Event Date: <input type="text" id="date" value="{{ date }}" readonly="readonly"
style="width: 251px"/></p>
<p>Keywords : <input type="text" id="keywords" value="{{keywords}}" readonly="readonly" /></p>
</div>
<script type="text/javascript">
Sys.Application.add_init(appInit);
function appInit() {
start();
}
</script>
start() is javascript method which is in file Service.js
when i run this programm it gives me error
js runtime error: 'object expected'
and debugger highlighted on
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/**xhtml**1-strict.dtd">
pls help me solve the problem
Use like this <% Html.RenderPartial("/Views/Templates/MyModule.ascx", Model); %> for passing the values using Model to a partial view MyModule.ascx. You can also use the method Html.RenderAction
When you use RenderPartial, you are by default passing the Model of your Index.aspx. Your partial view can then be of the same type. You can then use Model.MyParameter to find out which htm file you should be rendering. Otherwise you can pass it in the object parameter of the RenderPartial, and query that object inside of your partial view.

Issue with asp:ContentPlaceHolder and code blocks

When a content placeholder contains any code blocks it reports that the control collection is empty.
For instance:
MasterPage.aspx
<asp:ContentPlaceHolder ID="Content1" runat="server" />
<asp:ContentPlaceHolder ID="Content2" runat="server" />
<div>Content1: <%= Content1.Controls.Count %></div>
<div>Content2: <%= Content2.Controls.Count %></div>
APage.aspx
<asp:Content ContentPlaceHolderID="Content1" runat="server">
Plain text content.
</asp:Content>
<asp:Content ContentPlaceHolderID="Content2" runat="server">
<%= "Code block content." %>
</asp:Content>
This will render the following:
Plain text content. Code block content.
Content1: 1
Content2: 0
Why is the master page's ContentPlaceHolder.Controls collection empty?
I want to check whether the ContentPlaceHolder has been populated (see also this question) but can't if it contains any <%= blocks.
Does anyone know a way around this?
As promised, I said I would take a look. Sorry I never uploaded last night, long day and needed to hit the hay!
So, I was checking out the ContentPlaceHolder.Controls collection differences between how they are populated. I noticed that when the code block is used, it flips to read only. At any other point, it will simply be empty or populated.
I therefore decided to throw in an extension method to check it for us:
ContentPlaceHolderExtensions.cs
public static class ContentPlaceHolderExtensions
{
public static bool ContainsControlsOrCodeBlock(this ContentPlaceHolder placeHolder)
{
if (placeHolder.Controls.Count > 0)
return true;
return placeHolder.Controls.IsReadOnly;
}
}
And then check this in the master page:
Site.Master
<asp:ContentPlaceHolder ID="Content1" runat="server" />
<asp:ContentPlaceHolder ID="Content2" runat="server" />
<asp:ContentPlaceHolder ID="Content3" runat="server" />
<div>Content1: <%= Content1.Controls.Count %></div>
<div>Content2: <%= Content2.Controls.Count %></div>
<div>Content3: <%= Content3.Controls.Count %></div>
<div>Content1 (Ex. Meth.): <%= Content1.ContainsControlsOrCodeBlock() %></div>
<div>Content2 (Ex. Meth.): <%= Content2.ContainsControlsOrCodeBlock() %></div>
<div>Content3 (Ex. Meth.): <%= Content3.ContainsControlsOrCodeBlock() %></div>
As proof-of-concept, I then added a content page:
Index.aspx
<asp:Content ContentPlaceHolderID="Content1" runat="server">
Plain Text Content
</asp:Content>
<asp:Content ContentPlaceHolderID="Content2" runat="server">
<%= "Code block content" %>
</asp:Content>
And all rendered as expected (I believe)..
TBH, while it is not perfect.. I don't think we can get much more elegance in this situation. I am not sure how other control collections are set up in these different scenarios, so I only bolted on to the ContentPlaceHolder control.. Other templated controls may or may not work the same.
Thoughts?
You can download the project from here:
http://code.google.com/p/robcthegeek/source/browse/#svn/trunk/stackoverflow/964724
Too much for a comment, here's the full code that I finally got working (adapted from #Rob Cooper's answer):
public static bool HasContent( this ContentPlaceHolder placeHolder )
{
if ( placeHolder.Controls.Count > 0 )
{
LiteralControl textBlock;
ContentPlaceHolder subContent;
foreach ( var ctrl in placeHolder.Controls )
if ( (textBlock = ctrl as LiteralControl) != null )
{ //lit ctrls will hold any blocks of text
if ( textBlock.Text != null && textBlock.Text.Trim() != "" )
return true;
}
else if ( (subContent = ctrl as ContentPlaceHolder) != null )
{ //sub content controls should call this recursively
if ( subContent.HasContent() )
return true;
}
else return true; //any other control counts as content
//controls found, but all are empty
return false;
}
//if any code blocks are used the render mode will be different and no controls will
//be in the collection, however it will be read only
return placeHolder.Controls.IsReadOnly;
}
This includes two extra checks - firstly for empty literal controls (which occur if the page includes the <asp:Content tags with any whitespace between them) and then for sub-ContentPlaceHolder which will occur for any nested master pages.
The controls collection is empty because when <%= %> script tags are present, literal controls are not added to the control tree. However, server controls will still get added. So:
<asp:Content ID="Content2" ContentPlaceHolderID="Content2" Runat="Server">
<%= "Code block content." %>
<asp:GridView runat="server" ID="gvTest" />
</asp:Content>
<div>Content2: <%= Content2.Controls.Count %></div>
will return
Content 2: 1
Rick Strahl has a great article that explains this behavior:
To make code like this work, ASP.NET
needs to override the rendering of the
particular container in which any
script code is hosted. It does this by
using SetRenderMethodDelegate on the
container and creating a custom
rendering method ...
Rather than building up the control
tree literal controls, ASP.NET only
adds server controls to the control
tree when <% %> tags are present for a
container. To handle the literal
content and the script markup, ASP.NET
generates a custom rendering method.
This method then explicitly writes out
any static HTML content and any script
expressions using an HTML TextWriter.
Any script code (<% %>) is generated
as raw code of the method itself.
Unfortunately I can't think of any elegant solution to this conundrum.

Using embedded standard HTML forms with ASP.NET

I have a standard aspx page with which I need to add another standard HTML form into and have it submit to another location (external site), however whenever I press the submit button the page seems to do a post back rather than using the sub-forms action url.
A mock up of what the form relationships is below. Note in the real deployment the form will be part of a content area of a master page layout, so the form needs to submit independantly from the master page form.
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<form id="subscribe_form" method="post" action="https://someothersite.com" name="em_subscribe_form" >
<input type="text" id="field1" name="field1" />
<input id="submitsubform" type="submit" value="Submit" />
</form>
</div>
</form>
</body>
</html>
It's an interesting problem. Ideally you only want the 1 form tag on the page as other users have mentioned. Potentially you could post the data via javascript without having 2 form tags.
Example taken from here, modified for your needs. Not 100% sure if this will work for you but I think this is how you'll have to approach it.
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
<script type="text/javascript">
function postdata()
{
var fieldValue = document.getElementById("field1").value;
postwith("http://someothersite.com",{field1:fieldValue});
}
function postwith (to,p) {
var myForm = document.createElement("form");
myForm.method="post" ;
myForm.action = to ;
for (var k in p) {
var myInput = document.createElement("input") ;
myInput.setAttribute("name", k) ;
myInput.setAttribute("value", p[k]);
myForm.appendChild(myInput) ;
}
document.body.appendChild(myForm) ;
myForm.submit() ;
document.body.removeChild(myForm) ;
}
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<div>
<input type="text" id="field1" name="field1" />
<asp:Button ID="btnSubmitSubscribe" runat="server" Text="Submit" OnClientClick="postdata(); return false;" />
</div>
</div>
</form>
</body>
</html>
If javascript is not a viable option - you can use .Net's HttpWebRequest object to create the post call in code behind. Would look something like this in the code behind (assuming your text field is an asp textbox:
private void OnSubscribeClick(object sender, System.EventArgs e)
{
string field1 = Field1.Text;
ASCIIEncoding encoding=new ASCIIEncoding();
string postData="field1="+field1 ;
byte[] data = encoding.GetBytes(postData);
// Prepare web request...
HttpWebRequest myRequest =
(HttpWebRequest)WebRequest.Create("http://someotherwebsite/");
myRequest.Method = "POST";
myRequest.ContentType="application/x-www-form-urlencoded";
myRequest.ContentLength = data.Length;
Stream newStream=myRequest.GetRequestStream();
// Send the data.
newStream.Write(data,0,data.Length);
newStream.Close();
}
If you add an ASP.NET button to the form, and set its PostBackUrl property to the external site, then all the form data will be posted to that URL.
There is a very nice tricky solution for this problem.
You can insert a </form> tag before your <form> to close the asp.net form which causes the problem. Do not forget to add a <form> tag after your html form. It may cause the editor to give you an exception, but do not worry, it will work.
Nested forms are not possible in HTML according to the W3C. You can achieve your intended result using JavaScript or with jQuery as explained by Peter on a blog called My Thoughts.
In my experience, Appetere Web Solutions has the best solution. Simple and elegant...and it's not a hack. Use the PostBackUrl.
I just tried it and everything works as expected. I didn't want to use Javascript because I didn't want to include it in my Master Page for every page that uses it.
I had the same situation as Ross - except that my input types were all of the "hidden" varitey.
Cowgod's answer got me thinking about nested forms within my .aspx page. I ended up "un-nesting" my 2nd form OUT of the main .aspx form ( ) and placed it (along with my js script tags) just under the body tag - but before the main .aspx form tag.
Suddenly, everything was submitting as it was supposed to. Is this a hack?
ASP.NET allows you to have multiple forms on one page, but only one can be runat=server. However I don't think you can nest forms at all.
You might have to make a new master page, one without a form tag on it so the form will work on that one page only. This is not a good solution, unless you can place the form outside the master pages' form, and use javascript to submit the second form, but that's hardly better. There really is no good solution for what you are trying to achieve, and if so I'd like to hear it. I don't think you can do a POST call from a code-behind, can you? I'm sure there's some way. But that's probably the only solution: from code.

Resources