Click a button, call a method in controller in ASP.NET - asp.net

I want to do something very simple, which is to create an HTML button that calls a controller function when clicked, the same as this HTML actionlink. This should really be remarkably easy. The action link is:
#Html.ActionLink("Submit", "Submit", "Home")
I'm using the Razer viewmodel and .NET 4.5. I've done some research, and it seems that I may have to create my own custom button. I'm fine with that, but is that really necessary? See: Mvc Html.ActionButton. It would seem like an oversight for this to have no native microsoft support, but if not, I can live with that.
Please forgive the naivety of this question - I'm new to ASP.NET, though not to C# or web development. Thanks!

I grabbed this from somewhere. but you can map view actions to controller actions with the following code.
Create a class with the following code.
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false, Inherited=true)]
public class MultipleButtonAttribute : ActionNameSelectorAttribute
{
public string Name { get; set; }
public string Argument { get; set; }
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
var isValidName = false;
var keyValue = string.Format("{0}:{1}", Name, Argument);
var value = controllerContext.Controller.ValueProvider.GetValue(keyValue);
if (value != null)
{
controllerContext.Controller.ControllerContext.RouteData.Values[Name] = Argument;
isValidName = true;
}
return isValidName;
}
}
In your View code you can have the following submit buttons
<input type="submit" value="Action A" name="action:ActionA" />
<input type="submit" value="Action B" name="action:ActionB" />
And your controller contains the following code.
[HttpPost]
[MultipleButton(Name="action", Argument="ActionA")]
public ActionResult MyActionA(myModel model)
{
...
}
[HttpPost]
[MultipleButton(Name = "action", Argument = "ActionB")]
public ActionResult MyActionB(myModel model)
{
...
}

Related

Entering Value in Partial View and Posting it back to Main Controller in ASP.NET MVC 5 [duplicate]

I have a ViewModel that has a complex object as one of its members. The complex object has 4 properties (all strings). I'm trying to create a re-usable partial view where I can pass in the complex object and have it generate the html with html helpers for its properties. That's all working great. However, when I submit the form, the model binder isn't mapping the values back to the ViewModel's member so I don't get anything back on the server side. How can I read the values a user types into the html helpers for the complex object.
ViewModel
public class MyViewModel
{
public string SomeProperty { get; set; }
public MyComplexModel ComplexModel { get; set; }
}
MyComplexModel
public class MyComplexModel
{
public int id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
....
}
Controller
public class MyController : Controller
{
public ActionResult Index()
{
MyViewModel model = new MyViewModel();
model.ComplexModel = new MyComplexModel();
model.ComplexModel.id = 15;
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// model here never has my nested model populated in the partial view
return View(model);
}
}
View
#using(Html.BeginForm("Index", "MyController", FormMethod.Post))
{
....
#Html.Partial("MyPartialView", Model.ComplexModel)
}
Partial View
#model my.path.to.namespace.MyComplexModel
#Html.TextBoxFor(m => m.Name)
...
how can I bind this data on form submission so that the parent model contains the data entered on the web form from the partial view?
thanks
EDIT: I've figured out that I need to prepend "ComplexModel." to all of my control's names in the partial view (textboxes) so that it maps to the nested object, but I can't pass the ViewModel type to the partial view to get that extra layer because it needs to be generic to accept several ViewModel types. I could just rewrite the name attribute with javascript, but that seems overly ghetto to me. How else can I do this?
EDIT 2: I can statically set the name attribute with new { Name="ComplexModel.Name" } so I think I'm in business unless someone has a better method?
You can pass the prefix to the partial using
#Html.Partial("MyPartialView", Model.ComplexModel,
new ViewDataDictionary { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "ComplexModel" }})
which will perpend the prefix to you controls name attribute so that <input name="Name" ../> will become <input name="ComplexModel.Name" ../> and correctly bind to typeof MyViewModel on post back
Edit
To make it a little easier, you can encapsulate this in a html helper
public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string partialViewName)
{
string name = ExpressionHelper.GetExpressionText(expression);
object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
var viewData = new ViewDataDictionary(helper.ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo
{
HtmlFieldPrefix = string.IsNullOrEmpty(helper.ViewData.TemplateInfo.HtmlFieldPrefix) ?
name : $"{helper.ViewData.TemplateInfo.HtmlFieldPrefix}.{name}"
}
};
return helper.Partial(partialViewName, model, viewData);
}
and use it as
#Html.PartialFor(m => m.ComplexModel, "MyPartialView")
If you use tag helpers, the partial tag helper accepts a for attribute, which does what you expect.
<partial name="MyPartialView" for="ComplexModel" />
Using the for attribute, rather than the typical model attribute, will cause all of the form fields within the partial to be named with the ComplexModel. prefix.
You can try passing the ViewModel to the partial.
#model my.path.to.namespace.MyViewModel
#Html.TextBoxFor(m => m.ComplexModel.Name)
Edit
You can create a base model and push the complex model in there and pass the based model to the partial.
public class MyViewModel :BaseModel
{
public string SomeProperty { get; set; }
}
public class MyViewModel2 :BaseModel
{
public string SomeProperty2 { get; set; }
}
public class BaseModel
{
public MyComplexModel ComplexModel { get; set; }
}
public class MyComplexModel
{
public int id { get; set; }
public string Name { get; set; }
...
}
Then your partial will be like below :
#model my.path.to.namespace.BaseModel
#Html.TextBoxFor(m => m.ComplexModel.Name)
If this is not an acceptable solution, you may have to think in terms of overriding the model binder. You can read about that here.
I came across the same situation and with the help of such informative posts changed my partial code to have prefix on generated in input elements generated by partial view
I have used Html.partial helper giving partialview name and object of ModelType and an instance of ViewDataDictionary object with Html Field Prefix to constructor of Html.partial.
This results in GET request of "xyz url" of "Main view" and rendering partial view inside it with input elements generated with prefix e.g. earlier Name="Title" now becomes Name="MySubType.Title" in respective HTML element and same for rest of the form input elements.
The problem occurred when POST request is made to "xyz url", expecting the Form which is filled in gets saved in to my database. But the MVC Modelbinder didn't bind my POSTed model data with form values filled in and also ModelState is also lost. The model in viewdata was also coming to null.
Finally I tried to update model data in Posted form using TryUppdateModel method which takes model instance and html prefix which was passed earlier to partial view,and can see now model is bound with values and model state is also present.
Please let me know if this approach is fine or bit diversified!

Multiple buttons in the same form

I have one simple form with two buttons inside. Each button has to redirect me on different view in my controller. I was looking for some examples over the net, found solutions and implemented them. Unfortunately, it isn't working for me.
Controller:
public class HomeController : Controller
{
private MovieEntities db = new MovieEntities();
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpPost]
[Button(ButtonName = "clickButton", ButtonValue = "Send")]
public ActionResult Send()
{
return View();
}
[HttpPost]
[Button(ButtonName = "clickButton", ButtonValue = "Reset")]
public ActionResult Reset()
{
return View();
}
}
Index view:
#model IEnumerable<CustomWizzardMVC.Models.MovieInfo>
#{
ViewBag.Title = "Home";
}
<h1>Insert informations</h1>
#using(Html.BeginForm())
{
<input type="button" name="clickButton" value="Send" />
<input type="button" name="clickButton" value="Reset" />
}
Send and Reset view are just simple views with <p> tags inside.
I have Button class too:
public class Button : ActionNameSelectorAttribute
{
public string ButtonName { get; set; }
public string ButtonValue { get; set; }
public override bool IsValidName(ControllerContext controllerContext, string actionName, System.Reflection.MethodInfo methodInfo)
{
return controllerContext.HttpContext.Request[ButtonName] != null && controllerContext.HttpContext.Request[ButtonName] == ButtonValue;
}
}
What am I doing wrong in here? Also, if you know some other way to do the same functionality without using jQuery, please post some code :)
You can configure a form's target by it's action attribute.
So you can do this by changing your form's action attribute. You need to use client side script to do that.
An another option, you can send a value that contains user's option (like Option = "reset" or Option = "send"). And decide what view you need to go in your default view.
Change your input type="button" to type="submit.
<input type="button" /> buttons will not submit a form - they don't do anything by default. They're generally used in conjunction with JavaScript as part of an AJAX application.
<input type="submit"> buttons will submit the form they are in when the user clicks on them, unless you specify otherwise with JavaScript.
Found how it can be done. <input type="submit" value="Send" formaction="#Url.Action("Send","Home")" /> Just found out that formaction is the new HTML5 attribute that specifies the URL of the form for the HttpPost action. :)

Unobtrusive validation not working for custom validation attribute

I am trying to write a custom validation attribute in MVC and I can't make it work as an unobstructive validation. It works fine with postback (kinda) but as the form is on a dialog, I must have an Ajax style call or it's unusable. Maybe what i am trying to do is unachieveable. The problem is i need to connect to a database to do the check.
I made a simple demo for my problem.
My model
public class Customer
{
public int Id { get; set; }
[IsNameUnique]
[Required]
public string Name { get; set; }
}
The view:
#model WebApplication1.Models.Customer
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { #id = "NewForm" }))
{
#Html.ValidationSummary(true)
#Html.EditorFor(m => m.Name)
#Html.ValidationMessageFor(m => m.Name)
<br />
<input type="submit" value="Submit" />
}
Custom validation class
public class IsNameUnique : ValidationAttribute
{
private CustomerRepository _repository;
public IsNameUnique()
{
_repository = new CustomerRepository();
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if(value != null)
{
var isValid = _repository.IsNameUnique(value);
if(!isValid)
{
return new ValidationResult("Name must be unique");
}
}
return ValidationResult.Success;
}
}
Post method
[HttpPost]
public ActionResult Index(Customer customer)
{
if(ModelState.IsValid)
{
//add customer
}
return View();
}
database call
class CustomerRepository
{
internal bool IsNameUnique(object value)
{
//call to database
return false;
}
}
There is a form with a name field. I need to check if name is already in the database.
My question is how can I do unobtrusive style validation in my case? I found other posts on SO about IClientValidatable but none of them really show what I need to do. i.e. none of them do check against a database. Thanks.
Basically "unobtrusive validation" means "Client-Side validation, defined in an unobtrusive way". Key point here is "Client-Side", that is, validation which can be done via JavaScript in client browser.
Checking for name uniqueness involves server-side resources, and while it can be done in JavaScript using AJAX requests to server, usually people decide not to do so.
You can follow this guide for details of implementing unobtrusive validation: http://thewayofcode.wordpress.com/tag/custom-unobtrusive-validation/
In general you will need to do the following:
Enable unobtrusive validation in web.config
Include jQuery, jQuery Validate and unobtrusive scripts into your page
Implement IClientValidatable for your custom validation attribute
Implement and register client-side rules for your custom attribute
You may want to look into the [Remote] validation attribute. Just make a controller method that returns a JsonResult and map it to the remote attribute. This is probably the easiest way to accomplish what you're looking to do.
[Remote( "IsNameUnique", "Customers", HttpMethod = "post" )]
public override string Name { get; set; }
[HttpPost]
public JsonResult IsNameUnique( string name )
{
// Code
}
If you want to implement this as a custom validation, you need to do the following:
In your attribute, implement IClientValidatable. This requires you to implement GetClientValidationRules() method. Return a new client validation rule with your type and parameters.
Here's an example:
https://github.com/DustinEwers/dot-net-mvc-ui-demos/blob/master/ASPNET4/UIDemos/UIDemos/Attributes/PastDateOnlyAttribute.cs
Then you need to implement a jQuery validation rule. This is where you'd make your ajax call:
jQuery.validator.addMethod("pastdateonly", function (val, element, params) {
var value = $.trim($(element).val());
if (value === "") return true;
var maxDate = params.maxdate,
dteVal = new Date(value),
mxDte = new Date(maxDate);
return dteVal < mxDte;
});
Then add an unobtrusive adapter method.
jQuery.validator.unobtrusive.adapters.add("pastdateonly", ["maxdate"],
function (options) {
options.rules["pastdateonly"] = {
maxdate: options.params.maxdate
};
options.messages["pastdateonly"] = options.message;
}
);
Example:
https://github.com/DustinEwers/dot-net-mvc-ui-demos/blob/master/ASPNET4/UIDemos/UIDemos/Scripts/site.js

How to control the language in which model validation errors are displayed

In an ASP.NET MVC application, changing the Thread.CurrentThread.CurrentCulture[UI] can change the way MVC picks messages from resources. In the example application I've created to present the problem, there are two resource file - Res.resx for the English messages and Res.es.resx for the Spanish messages.
However, error messages resulting from the model validation always display in English.
My question is, how can I control the language in which the model validation error messages are displayed?
Below are parts of an example application I've wrote (based on the default ASP.NET MVC application) to demonstrate this problem.
Screenshots of how it looks in the browser:
https://dl.dropboxusercontent.com/u/4453002/SO_LanguageOfValidation.png
ViewModel and Controller - HomeController.cs :
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Threading;
using System.Web.Mvc;
namespace SO_ValidationMessageInEnglish.Controllers {
/// <summary>
/// A very basic view model.
/// </summary>
public class ViewModel {
[Display(Name = "Message", ResourceType = typeof(Res))]
[DisplayName("Message")]
[Required(AllowEmptyStrings = false, ErrorMessageResourceName = "MessageRequired", ErrorMessageResourceType = typeof(Res))]
public string Message { get; set; }
public string Language { get; set; }
}
public class HomeController : Controller {
public ActionResult Index(string language = "en") {
Thread.CurrentThread.CurrentCulture = new CultureInfo(language);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(language);
return View();
}
[HttpPost]
[ActionName("Index")]
public ActionResult IndexPost(ViewModel foo) {
Thread.CurrentThread.CurrentCulture = new CultureInfo(foo.Language ?? "en");
Thread.CurrentThread.CurrentUICulture = new CultureInfo(foo.Language ?? "en");
return View(foo);
}
}
}
View - Index.cshtml :
#model SO_ValidationMessageInEnglish.Controllers.ViewModel
#using SO_ValidationMessageInEnglish
#{ ViewBag.Title = Res.Title; }
#Res.CurrentMessage:<br />
<h2>#((Model != null) ? Model.Message : Res.Default)</h2>
<p />
#using (Html.BeginForm("Index", "Home", FormMethod.Post, null)) {
#Html.LabelFor(m => m.Message)
#Html.TextBoxFor(m => m.Message)
#Html.HiddenFor(m => m.Language)
#Html.ValidationMessageFor(m => m.Message)
<input type="submit" value="#Res.Submit" />
}
I also run into the same problem. When the model binder has invalid data it runs before the ActionFilter(s).
I didn't like the proposed solutions because messing with the routing was not my preferred solution. Listen for Application_AcquireRequestState is problematic because this event fire for each and every request, not just for requests that will be routed into MVC controllers.
I've end up writing a custom implementation of IControllerFactory that use DefaultControllerFactory internally and execute the localization code inside CreateController method.
This is not ideal either, hope it helps.
public class PluggableControllerFactory : IControllerFactory {
private readonly IControllerFactory innerControllerFactory;
public PluggableControllerFactory() {
innerControllerFactory = new DefaultControllerFactory();
}
public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName) {
// Run your culture localization here
return innerControllerFactory.CreateController(requestContext, controllerName);
}
public System.Web.SessionState.SessionStateBehavior GetControllerSessionBehavior(System.Web.Routing.RequestContext requestContext, string controllerName) {
return innerControllerFactory.GetControllerSessionBehavior(requestContext, controllerName);
}
public void ReleaseController(IController controller) {
innerControllerFactory.ReleaseController(controller);
}
}
}
.NET uses full culture code to pick up the resource file as first choice. Rename Res.es.resx to Res.es-ES.resx and see if that works.
I guess, one way to do that is to use your own model binder (although that might cause other problems in some situations). That will execute only when there is a model on the action, thus you might have to set the culture in two places (one for all actions and this for the model validation in particular):
public class MyModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
var un = HttpContext.Current.User.Identity.Name;
if (!string.IsNullOrWhiteSpace(un))
{
var userLanguageCode = .......
var culture = CultureInfo.GetCultureInfo(userLanguageCode ?? "en-US");
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
I found a different and in my opinion better solution that requires less work. In the Global.asax file:
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
// Run your culture localization here
}

How to pass complex type using json to ASP.NET MVC controller

I have a View that allows a user to enter/edit data for a new Widget. I'd like to form up that data into a json object and send it to my controller via AJAX so I can do the validation on the server without a postback.
I've got it all working, except I can't figure out how to pass the data so my controller method can accept a complex Widget type instead of individual parameters for each property.
So, if this is my object:
public class Widget
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
I'd like my controller method to look something like this:
public JsonResult Save(Widget widget)
{
...
}
Currently, my jQuery looks like this:
var formData = $("#Form1").serializeArray();
$.post("/Widget/Save",
formData,
function(result){}, "json");
My form (Form1) has an input field for each property on the Widget (Id, Name, Price). This works great, but it ultimately passes each property of the Widget as a separate parameter to my controller method.
Is there a way I could "intercept" the data, maybe using an ActionFilterAttribute, and deserialize it to a Widget object before my controller method gets called?
Thanks Jeff, that got me on the right path. The DefaultModelBinder is smart enough to do all the magic for me...my problem was in my Widget type. In my haste, my type was defined as:
public class Widget
{
public int Id;
public string Name;
public decimal Price;
}
Notice that the type has public fields instead of public properties. Once I changed those to properties, it worked. Here's the final source code that works correctly:
Widget.aspx:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Widget.aspx.cs" Inherits="MvcAjaxApp2.Views.Home.Widget" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<script src="../../Scripts/jquery-1.2.6.js" type="text/javascript"></script>
<script type="text/javascript">
function SaveWidget()
{
var formData = $("#Form1").serializeArray();
$.post("/Home/SaveWidget",
formData,
function(data){
alert(data.Result);
}, "json");
}
</script>
<form id="Form1">
<input type="hidden" name="widget.Id" value="1" />
<input type="text" name="widget.Name" value="my widget" />
<input type="text" name="widget.Price" value="5.43" />
<input type="button" value="Save" onclick="SaveWidget()" />
</form>
</asp:Content>
HomeController.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
namespace MvcAjaxApp2.Controllers
{
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
ViewData["Title"] = "Home Page";
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
ViewData["Title"] = "About Page";
return View();
}
public ActionResult Widget()
{
ViewData["Title"] = "Widget";
return View();
}
public JsonResult SaveWidget(Widget widget)
{
// Save the Widget
return Json(new { Result = String.Format("Saved widget: '{0}' for ${1}", widget.Name, widget.Price) });
}
}
public class Widget
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
}
Note that (in MrDustpan's solution) the parameter name widget in the MVC Action method must match with the prefix used in the name attribute in the ASPX file.
If this is not the case then the Action method will always receive a null object.
<input type="text" name="widget.Text" value="Hello" /> - OK
<input type="text" name="mywidget.Text" value="Hello" /> - FAILS
Phil Haack has a good blog post about model binding that might be helpful. Not 100% what you're talking about here, but I think it might give you a better overall understand about the DefaultModelBinder.
What you want to do is structure your javascript form object in the same way your backend object is structured:
{ Id : "id", Name : "name", Price : 1.0 }
Then use the toJSON plugin to convert it into the above string. You send this string to your backend and use something like the JayRock libraries to convert it to a new Widget object.

Resources