I just started playing with MVC and I've run into a roadblock. I'm using a partial view as a User Login flyout on the header of each page using OpenID. When the user clicks on the provider (similar to stackoverflow) it authenticates and then either returns to the calling page or redirects to the signup page. The code works flawlessly under Firefox and Chrome but bombs out in IE. The "provider" parameter in the controller is always sent as null. Is there some sort of bug involving posting input names/values in IE or am I doing something wrong?
This is what my openid partial view looks like:
<% using (Html.BeginForm("Authenticate", "Membership", new { ReturnUrl = Request.Url }, FormMethod.Post))
{
if (!Page.User.Identity.IsAuthenticated)
{ %>
<div class="openidProviders">
Log in or join using one of these OpenID providers:
<div class="large buttons">
<div class="provider"><div><%= Html.SubmitImage("provider", "/Content/common/images/google.gif", new { value = "Google" })%></div></div>
<div class="provider"><div><%= Html.SubmitImage("provider", "/Content/common/images/Yahoo.gif", new { value = "Yahoo" })%></div></div>
<div class="provider"><div><%= Html.SubmitImage("provider", "/Content/common/images/AOL.gif", new { value = "AOL" })%></div></div>
<div class="provider"><div><%= Html.SubmitImage("provider", "/Content/common/images/OpenId.gif", new { value = "OpenId" })%></div></div>
</div>
</div>
<% }
}
%>
And the controller logic is here:
[AcceptVerbs(HttpVerbs.Post), ValidateInput(false)]
public void Authenticate(string provider, string ReturnUrl)
{
// Figure out provider endpoint
// Authentication function calls here
}
Well, it looks like IE, for once, is the only browser properly following the HTML spec according to this post.
The HTML specification only requires
that x, y coordinates where the image
submit button was clicked be sent to
the web server. IE follows the
specification. The browsers that send
the value="..." parameter are doing
their own thing outside of the HTML
specification.
Basically, I need to use a submit input instead of SubmitImage and then style the background of the button accordingly. Not the optimal solution but at least it works. This is what the final solution looks like. If anyone knows a way of getting the SubmitImage to work properly, let me know.
Replace the buttons above with ones that look like this:
<input type="submit" value="Google" class="google_OpenID" name="provider" />
And the CSS class:
.google_OpenID
{
background-image: url(/Content/common/images/google.gif);
width:75px;
cursor:pointer;
color:transparent;
height:35px;
border:none;
}
Related
I am uploading a file using via a view linked to a controller however after the upload is uploaded the application is either trying to refresh or redirect and I need to prevent this. May you please point me in the right direction to avoid redirection and refresh? I have done a bit of reading and I suspect that this line action="/api/BulkUpload">might be causing the problem.
My Controller
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using Repositories.BulkUpload;
using Repositories.Interfaces.BulkUpload;
namespace SimSentinel.Controllers
{
//[Authorize]
public class BulkUploadController : ApiController
{
private readonly IBulkUploadRepository _bulkUploadRepository;
public async Task<HttpResponseMessage> PostFile()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/Files");
var provider = new FormDataStreamer(root);
try
{
StringBuilder sb = new StringBuilder(); // Holds the response body
// Read the form data and return an async task.
await Request.Content.ReadAsMultipartAsync(provider);
// This illustrates how to get the form data.
foreach (var key in provider.FormData.AllKeys)
{
foreach (var val in provider.FormData.GetValues(key))
{
sb.Append(string.Format("{0}: {1}\n", key, val));
}
}
// This illustrates how to get the file names for uploaded files.
foreach (var file in provider.FileData)
{
FileInfo fileInfo = new FileInfo(file.LocalFileName);
sb.Append(string.Format("Uploaded file: {0} ({1} bytes)\n", fileInfo.Name, fileInfo.Length));
}
return new HttpResponseMessage()
{
Content = new StringContent(sb.ToString())
};
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
public class FormDataStreamer : MultipartFormDataStreamProvider
{
public FormDataStreamer(string rootPath) : base(rootPath) { }
public FormDataStreamer(string rootPath, int bufferSize) : base(rootPath, bufferSize) { }
public override string GetLocalFileName(HttpContentHeaders headers)
{
var srcFileName = headers.ContentDisposition.FileName.Replace("\"", "");
return Guid.NewGuid() + Path.GetExtension(srcFileName);
}
}
}
}
MY HTML
<form name="form1" method="post" enctype="multipart/form-data" action="/api/BulkUpload">
<div>
<label for="caption">Image Caption</label>
<input name="caption" type="text" />
</div>
<div>
<label for="image1">Image File</label>
<input name="image1" type="file" />
</div>
<div>
<input type="submit" value="ok" />
</div>
</form>
You're correct. When you submit the form, the file is sent to the controller via a HTTP POST request and the page is either, necessarily, refreshed or redirected. If you don't want to the page to refresh or redirect, then you'll have to use AJAX to post the file to the controller.
From a Mozilla Developer document on HTTP requests,
The GET method requests a representation of the specified resource.
Requests using GET should only retrieve data.
The POST method is used to submit an entity to the specified
resource, often causing a change in state or side effects on the
server.
From these notes on Web Programming from Nanyang Technological University,
[The] POST request method is used to "post" additional data up to the server
(e.g., submitting HTML form data or uploading a file). Issuing an HTTP
URL from the browser always triggers a GET request. To trigger a POST
request, you can use an HTML form with attribute method="post" or
write your own network program. For submitting HTML form data, POST
request is the same as the GET request except that the URL-encoded
query string is sent in the request body, rather than appended behind
the request-URI.
So, you can see that since you're posting a file to the server using a standard HTTP request, it is necessarily going to refresh or redirect in some way.
To avoid this, you can use jQuery to asynchronously post the file to the server without refreshing the page. There are plenty of articles on how to do this. I suggest you give it a try and post another question if you get stuck.
Upload file using jQuery and post it to Controller
ASP Snippets - Upload file using jQuery AJAX in ASP.Net MVC
C# Corner - File Upload Through JQuery AJAX In ASP.NET MVC
Thanks so much for the help it guided me in the right direction. I eventually got my answer from this [How to submit html form without redirection? . The Iframe approach is the simplest approach it is a temporary fix seeing as some articles are saying that although it is still supported by most modern browsers it has been deprecated.
I am using ASP.Net MVC 4. I have multiple buttons on a view.. At present I am calling the same action method; and I am distinguishing the clicked button using a name attribute.
#using (Html.BeginForm("Submit", "SearchDisplay", new { id = Model == null ? Guid.NewGuid().ToString() : Model.SavedSearch }, FormMethod.Post))
{
<div class="leftSideDiv">
<input type="submit" id="btnExport" class="exporttoexcelButton"
name="Command" value="Export to Excel" />
</div>
<div class="pageWrapperForSearchSubmit">
<input type="submit" class="submitButton"
value="Submit" id="btnSubmitChange" />
</div>
}
//ACTION
[HttpPost]
public ActionResult Submit(SearchCostPage searchModel, string Command)
{
SessionHelper.ProjectCase = searchModel.ProjectCaseNumber;
if (string.Equals(Command, Constants.SearchPage.ExportToExcel))
{
}
}
QUESTIONS
Is there a way to direct to different POST action methods on different button clicks (without custom routing)?
If there is no way without custom routing, how can we do it with custom routing?
References:
Jimmy Bogard - Cleaning up POSTs in ASP.NET MVC
You can choose the url where the form must be posted (and thus, the invoked action) in different ways, depending on the browser support:
for newer browsers that support HTML5, you can use formaction attribute of a submit button
for older browsers that don't support this, you need to use some JavaScript that changes the form's action attribute, when the button is clicked, and before submitting
In this way you don't need to do anything special on the server side.
Of course, you can use Url extensions methods in your Razor to specify the form action.
For browsers supporting HMTL5: simply define your submit buttons like this:
<input type='submit' value='...' formaction='#Url.Action(...)' />
For older browsers I recommend using an unobtrusive script like this (include it in your "master layout"):
$(document).on('click', '[type="submit"][data-form-action]', function (event) {
var $this = $(this);
var formAction = $this.attr('data-form-action');
$this.closest('form').attr('action', formAction);
});
NOTE: This script will handle the click for any element in the page that has type=submit and data-form-action attributes. When this happens, it takes the value of data-form-action attribute and set the containing form's action to the value of this attribute. As it's a delegated event, it will work even for HTML loaded using AJAX, without taking extra steps.
Then you simply have to add a data-form-action attribute with the desired action URL to your button, like this:
<input type='submit' data-form-action='#Url.Action(...)' value='...'/>
Note that clicking the button changes the form's action, and, right after that, the browser posts the form to the desired action.
As you can see, this requires no custom routing, you can use the standard Url extension methods, and you have nothing special to do in modern browsers.
BEST ANSWER 1:
ActionNameSelectorAttribute mentioned in
How do you handle multiple submit buttons in ASP.NET MVC Framework?
ASP.Net MVC 4 Form with 2 submit buttons/actions
http://weblogs.asp.net/scottgu/archive/2007/12/09/asp-net-mvc-framework-part-4-handling-form-edit-and-post-scenarios.aspx
ANSWER 2
Reference: dotnet-tricks - Handling multiple submit buttons on the same form - MVC Razor
Second Approach
Adding a new Form for handling Cancel button click. Now, on Cancel button click we will post the second form and will redirect to the home page.
Third Approach: Client Script
<button name="ClientCancel" type="button"
onclick=" document.location.href = $('#cancelUrl').attr('href');">Cancel (Client Side)
</button>
<a id="cancelUrl" href="#Html.AttributeEncode(Url.Action("Index", "Home"))"
style="display:none;"></a>
This sounds to me like what you have is one command with 2 outputs, I would opt for making the change in both client and server for this.
At the client, use JS to build up the URL you want to post to (use JQuery for simplicity) i.e.
<script type="text/javascript">
$(function() {
// this code detects a button click and sets an `option` attribute
// in the form to be the `name` attribute of whichever button was clicked
$('form input[type=submit]').click(function() {
var $form = $('form');
form.removeAttr('option');
form.attr('option', $(this).attr('name'));
});
// this code updates the URL before the form is submitted
$("form").submit(function(e) {
var option = $(this).attr("option");
if (option) {
e.preventDefault();
var currentUrl = $(this).attr("action");
$(this).attr('action', currentUrl + "/" + option).submit();
}
});
});
</script>
...
<input type="submit" ... />
<input type="submit" name="excel" ... />
Now at the server side we can add a new route to handle the excel request
routes.MapRoute(
name: "ExcelExport",
url: "SearchDisplay/Submit/excel",
defaults: new
{
controller = "SearchDisplay",
action = "SubmitExcel",
});
You can setup 2 distinct actions
public ActionResult SubmitExcel(SearchCostPage model)
{
...
}
public ActionResult Submit(SearchCostPage model)
{
...
}
Or you can use the ActionName attribute as an alias
public ActionResult Submit(SearchCostPage model)
{
...
}
[ActionName("SubmitExcel")]
public ActionResult Submit(SearchCostPage model)
{
...
}
you can use ajax calls to call different methods without a postback
$.ajax({
type: "POST",
url: "#(Url.Action("Action", "Controller"))",
data: {id: 'id', id1: 'id1' },
contentType: "application/json; charset=utf-8",
cache: false,
async: true,
success: function (result) {
//do something
}
});
I created a simple form that works fine when I run it in Visual Studio, but fails on my website with a 404 error.
I have an Action set up to receive only "post" messages using [HttpPost].
If I change the Acton to receive "get" messages, the Action is called, but the form information is not passed along.
This site is written with .Net MVC, could there be some kind of security or something on the server that may need to be changed to allow for "post" calls?
Controller Code -
[HttpGet]
public ActionResult Tester1()
{
return View("Tester1");
}
[HttpPost]
public ActionResult Tester2(MyModel myModel)
{
return View("Tester2", myModel);
}
Tester1.cshtml Code -
#model FS.Models.MyModel
#using (Html.BeginForm("Tester2", "Comics", FormMethod.Post))
{
<div>
Enter Text -
#Html.TextBoxFor(m => m.property1)
</div>
<div>
<input type="submit" value="SEND!" />
</div>
}
Tester2.cshtml Code -
#model FS.Models.MyModel
You entered - #Model.property1
Global.asax.cs Code -
routes.MapRoute(
"Tester2Route", // Route name
"{controller}/Tester2", // URL with parameters
new { controller = "Comics", action = "Tester2" } // Parameter defaults
);
(the code above is just a summarized version, you can visit the actual site here - http://funkysmell.com/Comics/Tester1/)
I was able to get this to work.
The problem is that the server you are running on is wanting a trailing slash (/) after the URL. So when I looked at your example, the URL generated is
<form action="/Comics/Tester2" method="post">
If you add a trailing slash it will work. i.e.
<form action="/Comics/Tester2/" method="post">
Take a look here to get more information.
Why is ASP.NET MVC ignoring my trailing slash?
There are links in that answer to a few blogs which should help you out.
I have this controller:
public ActionResult Index(int id)
{
var cust = (from c in dataModel.Customers
where (c.MembershipID == id)
select c).First();
return View(cust);
}
I want to be able to pass through the ID from a text box on the main page. I tried the following but it says 'memberid' does not exist. Any ideas? Thanks.
<asp:TextBox ID="memberid"/>
<%: Html.ActionLink("Customer", "Index", new {id = memberid.Text}) %>
My goal is to Enter a value in a textbox, click a button and then be redirected to a new view showing that users details.
This is only possible using Javascript; you can handle the link's click event and explicitly navigate to the URL.
Using jQuery:
$('#link').click(function() {
location = "/Customer/Index/" + encodeUriComponent($('#memberId').text());
});
If you want to do it without Javascript, you can make a form containing a textbox and an <input type="submit" />.
Since you want the user to click a button, change the link to a button and in the http post method for your main page, Redirect to the Customer/Index view passing in the memberId.
So I have been waxing lyrical about a ASP.NET MVC to a friend who is about to start development of a new user interface....
He asked me if you could solve the following problem with ASP.NET MVC:
Imagine a web app that supports plugins. In the current ASP.NET WebForms app the pluggin developer provides a usercontrol and some JQuery.
The IDs of the controls are unique so that the JQuery can always select the correct DOM elements and so that the code behind can deal with the correct control collections.
I suggested that in MVC since we can have any number of forms... each plugin could be implemented as a partialView.
Each partialView would be wrapped by its own form so the relevant Controller Action and therefore would only receive form data defined in the partialView - so from this point of view we dont care about DOM id collisions.
However the HTML would be invalid if ID collision did occur and hence JQuery written by the plugin developer could fail!
I'm not sure how we could get round this...
I dont like the idea of parsing the partialView for collisions when the plugin is added and I dont like the idea of restricting the ids that the plugin developer has access to.
Maybe the the ids could be augmented with a prefix at run time and the model binders could be provided with this prefix?
You could just wrap the contents of the plugin within a DIV or FORM element and give that a unique ID on the page. Then just use jQuery to only select elements that are within this "parent" DIV or FORM element.
You could probably auto generate a GUID to use as the unique ID at runtime, but this would require some effort by the person writing the plugin. Although, you could probably architect it out in a way to make it automatically generate the "parent" DIV and ID, then you could just access the ID within the view as a Property of the Plugin.
Just some thoughts, I haven't built a an ASP.NET MVC plugin based system like this yet, but it doesn't seem too difficult.
Here's an example of a PartialView that uses a custom ViewUserControl base class:
ViewUserControl1.ascx:
<%# Control Language="C#" Inherits="MvcPluginPartialView.PluginViewUserControl" %>
<input class="txtText" type="text" value="<%=this.ID %>" />
<input class="txtButton" type="button" value="Show Alert" />
<script type="text/javascript">
jQuery(function() {
// This is the Unique ID of this Plugin on the Page
var pluginID = "<%=this.ID %>";
// Attach the OnClick event of the Button
$("#" + pluginID + " .txtButton").click(function() {
// Display the content of the TextBox in an Alert dialog.
alert($("#" + pluginID + " .txtText").val());
});
});
</script>
MvcPluginPartialView.PluginViewUserControl:
namespace MvcPluginPartialView
{
public class PluginViewUserControl : ViewUserControl
{
public PluginViewUserControl()
{
this.ID = "p" + Guid.NewGuid().ToString().Replace("-", "");
}
public override void RenderView(ViewContext viewContext)
{
viewContext.HttpContext.Response.Cache.SetExpires(DateTime.Now);
ViewUserControlContainerPage containerPage = new ViewUserControlContainerPage(this);
//this.ID = Guid.NewGuid().ToString();
RenderViewAndRestoreContentType(containerPage, viewContext);
}
internal static void RenderViewAndRestoreContentType(ViewPage containerPage, ViewContext viewContext)
{
string contentType = viewContext.HttpContext.Response.ContentType;
containerPage.RenderView(viewContext);
viewContext.HttpContext.Response.ContentType = contentType;
}
private sealed class ViewUserControlContainerPage : ViewPage
{
public ViewUserControlContainerPage(ViewUserControl userControl)
{
this.Controls.Add(userControl);
}
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
writer.Write("<div id='" + this.Controls[0].ID + "'>");
base.Render(writer);
writer.Write("</div>");
}
}
}
}
Then to place the View on the page you can use the "Html.RenderPartial" method as usual, plus you can place as many of them on the Page as you want and they'll all work as expected.
<%Html.RenderPartial("ViewUserControl1"); %>
<%Html.RenderPartial("ViewUserControl1"); %>
<%Html.RenderPartial("ViewUserControl1"); %>