ASP.NET MVC, a plugin architecture and id collisions - asp.net-mvc

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"); %>

Related

Posting form to different MVC post action depending on the clicked submit button

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
}
});

asmx web service and MVC 3 - How to use Model and map web service data to it?

I have a web service and I want to display the data from web service in my MVC Razor View.
This is what I have done:
1) My Web Method:
[WebMethod]
public string HelloWorld()
{
return "Hello World... This is a Web Service consumed
through MVC Project";
}
2) Added web reference to my MVC Project
3) View :
<table><tr><td>
<input type="button" id="btnSubmit" value="Get Message"
onclick="javascript:getMessage();" />
</td></tr></table>
<div id="Result"></div>
4) Script in my view
function getMessage() {
var URL = "/Home/getMessage/";
$.get(URL, function (data) {
$("#Result").html(data);
});
}
Note : Controller name is Home and Action Method is getMessage
5) Action Method in Home
public string getMessage()
{
Service1 mvcServiceProxy = new Service1();
string message = mvcServiceProxy.HelloWorld();
return message;
}
I have followed the above steps and I am able to get the message in to my DIV as per my javascript code.
But If I have a model, and the property in my model is like: public string Message{ get; set; }
How can I get the message into this property? DO I need to modify my action method and Javascript? Should I use something like JSON ?
I am not sure of how to achive this...
I just want to use my property and display the content (message) from my web service into my Razor view using my model property instead of passing the html value into DIV and directly displaying it.
Please suggest.
Thanks in advance !!!!
First of all property is basically used to read/modify any private data in a class from another Type.
So what you are trying to achieve shouldn't be done with properties.
The way you are trying is ok, also you can get a complete list of data in one call using Json and set it on html page according to your need.
So I would recommend playing around with Json.

What are the security reasons for not allowing get requests with MVC/ajax? [duplicate]

As part of the ASP.NET MVC 2 Beta 2 update, JSON GET requests are disallowed by default. It appears that you need to set the JsonRequestBehavior field to JsonRequestBehavior.AllowGet before returning a JsonResult object from your controller.
public JsonResult IsEmailValid(...)
{
JsonResult result = new JsonResult();
result.Data = ..... ;
result.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
return result;
}
What is the reasoning behind this? If I am using JSON GET to try and do some remote validation, should I be using a different technique instead?
The reason for the DenyGet default is on MSDN with a link to Phil Haack's blog for further details. Looks like a Cross-Site scripting vulnerability.
HTTP GET is disabled by default as part of ASP.NET's Cross-Site Request Forgery (CSRF/XSRF) protections. If your web services accept GET requests, then they can be vulnerable to 3rd party sites making requests via <script /> tags and potentially harvesting the response by modifying JavaScript setters.
It is worth noting however that disabling GET requests is not enough to prevent CSRF attacks, nor is it the only way to protect your service against the type of attack outlined above. See Robust Defenses for Cross-Site Request Forgery for a good analysis of the different attack vectors and how to protect against them.
I also had your problem when I migrated my MVC website from Visual Studio 2008 to Visual Studio 2010.
The main aspx is below, it has an ViewData which calls a Category Controller in order to fill up ViewData["Categories"] with SelectList collection. There's also a script to call a Subcategory Controller to fill up the second combo with javascript. Now I was able to fix it adding up AlloGet attribute on this second controller.
Here's the aspx and javascript
<head>
<script type="text/javascript" src="../../Scripts/jquery-1.4.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$("#CategoryId").change(function () {
var categoryId = $(this)[0].value;
$("#ctl00_MainContent_SubcategoryId").empty();
$("#ctl00_MainContent_SubcategoryId").append("<option value=''>-- select a category --</option>");
var url = "/Subcategory/Subcategories/" + categoryId;
$.getJSON(url, { "selectedItem": "" }, function (data) {
$.each(data, function (index, optionData) {
$("#ctl00_MainContent_SubcategoryId").append("<option value='" + optionData.SubcategoryId + "'>" + optionData.SubcategoryName + "</option>");
});
//feed our hidden html field
var selected = $("#chosenSubcategory") ? $("#chosenSubcategory").val() : '';
$("#ctl00_MainContent_SubcategoryId").val(selected);
});
}).change();
});
</script>
<body>
<% using (Html.BeginForm()) {%>
<label for="CategoryId">Category:</label></td>
<%= Html.DropDownList("CategoryId", (SelectList)ViewData["Categories"], "--categories--") %>
<%= Html.ValidationMessage("category","*") %>
<br/>
<label class="formlabel" for="SubcategoryId">Subcategory:</label><div id="subcategoryDiv"></div>
<%=Html.Hidden("chosenSubcategory", TempData["subcategory"])%>
<select id="SubcategoryId" runat="server">
</select><%= Html.ValidationMessage("subcategory", "*")%>
<input type="submit" value="Save" />
<%}%>
here's my controller for subcategories
public class SubcategoryController : Controller
{
private MyEntities db = new MyEntities();
public int SubcategoryId { get; set; }
public int SubcategoryName { get; set; }
public JsonResult Subcategories(int? categoryId)
{
try
{
if (!categoryId.HasValue)
categoryId = Convert.ToInt32(RouteData.Values["id"]);
var subcategories = (from c in db.Subcategories.Include("Categories")
where c.Categories.CategoryId == categoryId && c.Active && !c.Deleted
&& c.Categories.Active && !c.Categories.Deleted
orderby c.SubcategoryName
select new { SubcategoryId = c.SubcategoryId, SubcategoryName = c.SubcategoryName }
);
//just added the allow get attribute
return this.Json(subcategories, JsonRequestBehavior.AllowGet);
}
catch { return this.Json(null); }
}
I don't know if this is the reason they chose to change that default, but here's my experience:
When some browsers see a GET, they think they can cache the result. Since AJAX is usually used for small requests to get the most up-to-date information from the server, caching these results usually ends up causing unexpected behavior. If you know that a given input will return the same result every time (e.g. "password" cannot be used as a password, no matter when you ask me), then a GET is just fine, and browser caching can actually improve performance in case someone tries validating the same input multiple times. If, on the other hand, you expect a different answer depending on the current state of the server-side data ("myfavoriteusername" may have been available 2 minutes ago, but it's been taken since then), you should use POST to avoid having the browser thinking that the first response is still the correct one.

ASP.NET Mvc 2 How do you launch client side validation from inside javascript?

Is there a function for ASP.NET MVC 2 built in data annotation javascript validation that performs the functionality of Jquery.Validate's isValid()?
I'd like to check if my fields are valid prior to using jquery ajax to send data to the server? Any suggestions?
Thank You.
i used the :
http://geekswithblogs.net/stun/archive/2010/02/27/asp.net-mvc-client-side-validation-summary-with-jquery-validation-plugin.aspx
and it worked great for me,
especially you don't need to change the original Mvc Validation way(I mean the validation field), you just make it client side
As basilmir and Dom Ribaut implies you should get this automatically if you EnableClientValidation(). However if you want to manually call client side MVC validation you can use:
if (!Sys.Mvc.FormContext.getValidationForForm($("#myform").get(0)).validate('submit').length) {
// is valid
}
You can replace $("#myform").get(0) with the DOM element for your form.
Seems that there is nothing special in MicrosoftMvcJQueryValidation.js except for registration of rules for jquery.validate.js plugin.
This worked for me:
<script type="text/javascript">
function validateForm(formId)
{
var valid = $("#" + formId).validate().form();
return valid;
}
</script>
Scott Guh describe simple js validation step by step in this post: http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx (look for step3).
It is not JQuery but wouldn't that fit your needs?
--
Dom
Have a look at xval. It lets you define your validation rules either using data annotation attributes or castle validation attributes (I think nhibernate validation has also been added recently). Validation is then converted to client validation rules and you can validate a form using ajax so no postback to the server.
From the project page: xVal is a validation framework for ASP.NET MVC applications. It makes it easy to link up your choice of server-side validation mechanism with your choice of client-side validation library, neatly fitting both into ASP.NET MVC architecture and conventions.
If you are only after validation mechanisms for asp.net mvc then have a look at this and this
Jquery will be your best friend
check this http://bassistance.de/jquery-plugins/jquery-plugin-validation/
document link:http://docs.jquery.com/Plugins/Validation
You can enable client-side validation via <% Html.EnableClientValidation(); %>
It will automatically generate all the javascript code you need to the server-side validation to work on the client-side. Remember to still check on the server-side, since the client can bypass javascript and send bad data. Do not use client-side validation only.
<% Html.EnableClientValidation(); %>
<%= Html.ValidationSummary() %>
<% using (Html.BeginForm()) {%>
<%=Html.EditorForModel() %>
<p>
<input type="submit" value="Save" />
</p>
<% } %>
here is a simple program will guide how to do client side validation of Form in JavaScript.
Name : <asp:TextBox ID="txtName" />
Email : <asp:TextBox ID="txtEmail" />
Web URL : <asp:TextBox ID="txtWebUrl" />
Zip : <asp:TextBox ID="txtZip" />
<asp:Button ID="btnSubmit" OnClientClick=" return validate()" runat="server" Text="Submit" />
Now on the source code of this form in script tag write the following code:
<script language="javascript" type="text/javascript">
function validate()
{
if (document.getElementById("<%=txtName.ClientID%>").value=="")
{
alert("Name Feild can not be blank");
document.getElementById("<%=txtName.ClientID%>").focus();
return false;
}
if(document.getElementById("<%=txtEmail.ClientID %>").value=="")
{
alert("Email id can not be blank");
document.getElementById("<%=txtEmail.ClientID %>").focus();
return false;
}
var emailPat = /^(\".*\"|[A-Za-z]\w*)#(\[\d{1,3}(\.\d{1,3}){3}]|[A-Za-z]\w*(\.[A-Za-z]\w*)+)$/;
var emailid=document.getElementById("<%=txtEmail.ClientID %>").value;
var matchArray = emailid.match(emailPat);
if (matchArray == null)
{
alert("Your email address seems incorrect. Please try again.");
document.getElementById("<%=txtEmail.ClientID %>").focus();
return false;
}
if(document.getElementById("<%=txtWebURL.ClientID %>").value=="")
{
alert("Web URL can not be blank");
document.getElementById("<%=txtWebURL.ClientID %>").value="http://"
document.getElementById("<%=txtWebURL.ClientID %>").focus();
return false;
}
var Url="^[A-Za-z]+://[A-Za-z0-9-_]+\\.[A-Za-z0-9-_%&\?\/.=]+$"
var tempURL=document.getElementById("<%=txtWebURL.ClientID%>").value;
var matchURL=tempURL.match(Url);
if(matchURL==null)
{
alert("Web URL does not look valid");
document.getElementById("<%=txtWebURL.ClientID %>").focus();
return false;
}
if (document.getElementById("<%=txtZIP.ClientID%>").value=="")
{
alert("Zip Code is not valid");
document.getElementById("<%=txtZIP.ClientID%>").focus();
return false;
}
var digits="0123456789";
var temp;
for (var i=0;i<document.getElementById("<%=txtZIP.ClientID %>").value.length;i++)
{
temp=document.getElementById("<%=txtZIP.ClientID%>").value.substring(i,i+1);
if (digits.indexOf(temp)==-1)
{
alert("Please enter correct zip code");
document.getElementById("<%=txtZIP.ClientID%>").focus();
return false;
}
}
return true;
}
</script>
And in code behind file just write the below code.
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
btnSubmit.Attributes.Add("onclick", "return validate()")
End Sub
Now you will get a form with proper validation.
I hope this is going to help you

Internet Explorer Post Data Bug - ASP.NET MVC

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;
}

Resources