Using ASP.NET MVC, in my view, I list some files and show them as a button. By clicking each button the corresponding file should be downloaded.
To show the list of files, I pass a model to view and when a user clicks on each of those buttons, I have to send the filename plus the original model back to the controller.
I found answers for a similar question but in my case, I don't have only one button. I have one button for each filename that I render on view.
This is the code for my view:
#using (Html.BeginForm("DownloadFile", "SharedFolder", FormMethod.Post))
{
<div class="col-sm-6">
<div class="panel panel-info">
<div class="panel-heading">Files</div>
<div class="panel-body" style="max-height:300px; height:300px; overflow-y:scroll">
#foreach (var file in Model.Files)
{
<button type="button" class="btn btn-link btn-sm" onclick="location.href='#Url.Action("DownloadFile", "SharedFolder", new { fileToDownload = file, data = Model })'">
<div class="glyphicon glyphicon-file" style="color:dodgerblue">
<span style="color:black;">#file</span>
</div>
</button>
<br />
}
</div>
</div>
</div>
}
And my controller action:
[HttpPost]
public ActionResult DownloadFile(string fileToDownload, FolderExplorerViewModel data)
{
// download the file and return Index view
return RedirectToAction("Index");
}
When I click on a file to download it, I get below error:
The resource cannot be found.
Requested URL: /SharedFolder/DownloadFile
Update:
This is my ViewModel
public class FolderExplorerViewModel
{
public int ID { get; set; }
public List<Folder> Folders { get; set; }
public List<string> Files { get; set; }
public string SelectedPath { get; set; }
}
You shouldn't store data = Model like this causes the performance and security problem.
You just store one fileToDownload value from View. After that, in Controller, You should get file by fileToDownload param.
[HttpPost]
public ActionResult DownloadFile(string fileToDownload)
{
// download the file by `fileToDownload` param here
return RedirectToAction("Index");
}
The reason why you are getting that error is because, its trying to do form post and there are no form html elements representing fileToDownload and data.
Actually, instead of using form POST you can use anchor tags invoking controller GET action (since its just download, no need to use POST here). As #Phong mentioned, using fileToDownload you probably can retrieve other information which is needed for redirection to Index.
#foreach (var file in Model.Files)
{
#file
}
and then your controller action will be:
public ActionResult DownloadFile(string fileToDownload)
{
// do the stuff
// download would return FileResult on success, not sure what you meant by RedirectToAction("Index")
// download the file and return Index view
return RedirectToAction("Index");
}
If you still wanted to send Model, then you can do that via javascript button click event with ajax.
Related
I am having a problem returning values to the controller when using a ViewModel.
For clarity I have simplified the code below where the original has many more fields.
When the page is loaded, the value in the hidden field is as expected. However when the form is submitted the value in the field is not being sent and instead I get an ArgumentNullException.
Please can you advise on what I am doing wrong.
View
#model Project.Models.SCView
#using (Html.BeginForm("ScorecardEdit"))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.FV.ID)
<input type="submit" value="Save" />
}
Model
public class FixView
{
public int ID { get; set; }
[DisplayFormat(DataFormatString = "{0:ddd dd/MM/yyyy}")]
public DateTime MatchDate { get; set; }
}
public class SCView
{
public FixView FV { get; set; }
public SCView()
{
this.FV = new FixView();
}
}
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ScorecardEdit(SCView ReturnSC)
{
}
The code that you have should be working as MVC should be able to map your FV.ID property as expected as the HiddenFor() helper will generate the proper names to handle mapping it :
Using the same code that you provided, the debugger demonstrated the following after submitting the form :
The issue here sounds like you have a few other properties, possibly collection-based ones that use the DropDownListFor() helpers that have collections which are not posted to the server, so when you attempt to use the model you have to render populate one of those helpers, you are getting your ArgumentNullException.
I have this button:
<button type="button" onclick="location.href='#Url.Action("Index", "Registration">Register</button>
Its on a sign-in page that has this link which goes to a registration page. I'd like to preserve the username and password entered on this page when the user clicks the link to go to the registration page, so is there a way to pass it a new model such that the user goes to the registration page and already has their ID and password entered?
For example, I am trying to do something like this:
<button type="button" onclick="location.href='#Url.Action("Index", "Registration", new RegistrationModel { ID = Model.ID, Password = Model.Password })'">Register</button>
All you need to do is just post your values to which ever controller you want to handle it and share the same textbox names.
Say you have a view like so, containing your form:
#model LoginFormViewModel
#* Form element defaults to posting to Login controller, but has an attribute containing the URL to the Registration controller should we need it *#
<form id="login_form" method="post" action="#Url.Action("Index", "Login")" data-registration-url="#Url.Action("Index", "Registration")">
#* Username field *#
#Html.TextBoxFor(x => x.Username)
#* Password field *#
#Html.PasswordFor(x => x.Password, new { value = Model.Password })
#* Hidden value that we can check in the controller *#
<input type="hidden" name="FromLoginPage" value="true" />
<input type="submit" value="Register" id="register_submit" />
<input type="submit" value="Login" />
</form>
I would use jQuery to control the submitting of the forms:
// User clicked regstration button, not login button
$(document).delegate('input#register_submit', 'click', function(e) {
// Prevent default submitting of the form to the login controller
e.preventDefault();
// Get registration URL from the attribute
var registerActionUrl = $('form#login_form').attr('data-registration-url');
// Submit the form to the registration controller
$('form#login_form').attr('action', registerActionUrl).submit();
});
Here's the model LoginViewModel and RegistrationViewModel that share properties with the same names (Username and Password), this will come in handy for binding up depending on which controller we submit the form to:
// Login model
public LoginViewModel
{
public string Username { get; set; }
public string Password { get; set; }
}
// Registration view model
public RegistrationViewModel
{
public string Username { get; set; }
public string Password { get; set; }
public bool FromLoginPage { get; set; }
// ... other fields that won't get bound when submitted from login page
public string ConfirmPassword { get; set; }
}
In the Login controller we handle things normally, but within the registration form we can do a quick check on FromLoginPage submitted from the login page to return the registration page back to the user with the fields populated:
// Login controller
[HttpPost]
public LoginController : Controller
{
public Index(LoginViewModel requestmodel)
{
// Validate login
// Process login
// Return the view
return View(requestmodel);
}
}
// Registration controller
[HttpPost]
public RegistrationController : Controller
{
public Index(RegistrationViewModel requestModel)
{
// Submitted from the login page?
if (requestModel.FromLoginPage)
{
// Clear any model validation errors so far
this.ModelState.Clear();
// Just load the registration page with the values populated
return View(requestmodel);
}
// A normal registration request from registration page, perform validation on entire model
// Process login here
return View(requestmodel);
}
}
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. :)
so I have a Url Action
Create new teacher & assign to account.
That passes in two routeValues: createAndAssign, and teacherID.
Now when I go to my Teacher/Create page, my URL is like so:
.../Teacher/Create?createAndAssign=True&teacherID=ea817321-5633-4fdc-b388-5dba2c4a728e
Which is good, I want this. Now when I POST to create my teacher, how do I grab createAndAssign and teacherID value?
You can set the Querystring value in a hidden variables in the form and render in your GET action method and accept that in your POST action method.
View rendered by your GET Action
#using (Html.BeginForm())
{
//Other form elements also
#Html.Hidden("teacher",#Request.QueryString["teacherID"] as string)
#Html.Hidden("createAndAssign",#Request.QueryString["createAndAssign"]
as string)
<input type="submit" />
}
and now have a teacher parameter and createAndAssign parameter in your HttpPost action method so that it will be available when you submit the form.
[HttpPost]
public ActionResult Create(string teacher,string createAndAssign)
{
//Save and Redirect
}
If your view is strongly typed (which is my personal preference), it is quite easy,
public ActionResult GET(string teacherID,string createdAndAssing)
{
var yourVMObject=new YourViewModel();
yourVMObject.TeacherID=teacherID;
yourVMObject.CreateAndAssign=createdAndAssing;
return View(createdAndAssing);
}
and in your strongly typed view,
#model YourViewModel
#using (Html.BeginForm())
{
//Other form elements also
#Html.HiddenFor(x=>x.TeacherID)
#Html.HiddenFor(x=>x.CreateAndAssign)
<input type="submit" />
}
And in your POST action
[HttpPost]
public ActionResult Create(YourViewModel model)
{
//look for model.TeacherID
//Save and Redirect
}
you can get the value from the query string or as params of the controller like
var x =Request.QueryString["createAndAssign"];
or
public ActionResult Create(bool createAndAssign, string teacherID){
return View();
}
I have a view with three submit buttons. First button must validate some fields, second button must validate other fields, third button doesn't validate anything.
How can I do that on both client (unobtrusive) and server sides?
Most likely with alot of hand rolled usage, especially if you want it to be unobtrusive. You're going to need to create something like <input data-validationgroup="group1"... and then on the click action that your javascript code will then validate what you want. I would imagine jQuery Validate has some type of ruleset support but you'll have to figure that out.
You're going to have to do a similar sequence on the server side, and create ruleset type validation classes/blocks/methods that you interpret the submit action to the relevant ruleset. I'd look at a library like FluentValidation for this part.
To achieve what you want it, is is very extremely unlikely you will be able to achieve this using the DataAnnotations attributes on your model class.
Personally I have always liked and used the FluentValidation.NET library in all my projects. Not only that it is very powerful in terms of expressing validation rules but this library has an excellent integration with ASP.NET MVC. So I will try to provide a sample solution for this problem using it (only server side validation for the moment, later we can talk about unobtrusive client side validation if you want).
So start a new ASP.NET MVC 3 project using the default template and install the FluentValidation.MVC3 NuGet package (the current stable version is 2.0.0.0).
Then let's define a view model:
public class MyViewModel
{
public string Field1 { get; set; }
public string Field2 { get; set; }
}
Now we can assume that if button1 is clicked Field1 is required and if button2 is clicked Field2 is required and if button3 is clicked none of them are required. A fictional scenario but pretty close to your requirements.
Now let's define two different fluent validators for this model corresponding each to button1 and button2:
public class MyModelValidator1 : AbstractValidator<MyViewModel>
{
public MyModelValidator1()
{
RuleFor(x => x.Field1)
.NotEmpty();
}
}
public class MyModelValidator2 : AbstractValidator<MyViewModel>
{
public MyModelValidator2()
{
RuleFor(x => x.Field2)
.NotEmpty();
}
}
Now because it is only at runtime that we know which button is clicked we need to apply the correct validator based on value in the request. So let's write a custom validator provider factory:
public class MyFactory : IValidatorFactory
{
private readonly Func<HttpContextBase> _contextProvider;
public MyFactory(Func<HttpContextBase> contextProvider)
{
_contextProvider = contextProvider;
}
public IValidator GetValidator(Type type)
{
if (type == typeof(MyViewModel))
{
var context = _contextProvider();
if (!string.IsNullOrEmpty(context.Request["button1"]))
{
return new MyModelValidator1();
}
if (!string.IsNullOrEmpty(context.Request["button2"]))
{
return new MyModelValidator2();
}
}
return null;
}
public IValidator<T> GetValidator<T>()
{
return (IValidator<T>)GetValidator(typeof(T));
}
}
and register it in Application_Start:
ModelValidatorProviders.Providers.Add(
new FluentValidationModelValidatorProvider(
new MyFactory(() => new HttpContextWrapper(HttpContext.Current))
)
);
and that's pretty much all. Now what's left is trivial.
A controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
return Content("Thanks for submitting", "text/plain");
}
}
and a view:
#model MyViewModel
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(x => x.Field1)
#Html.EditorFor(x => x.Field1)
#Html.ValidationMessageFor(x => x.Field1)
</div>
<div>
#Html.LabelFor(x => x.Field2)
#Html.EditorFor(x => x.Field2)
#Html.ValidationMessageFor(x => x.Field2)
</div>
<input type="submit" value="Submit with button 1" name="button1" />
<input type="submit" value="Submit with button 2" name="button2" />
<input type="submit" value="Submit with button 3" name="button3" />
}