MVC noob here and cannot find a simple enough explanation from this.
I just started working on a fairly large app built with MVC.
In the controller I'm using, most of the ActionResults have the [HttpGet] attribute appended to them. So I'm building off that code, and I built two ActionResults myself but left the [HttpGet] attributes off.
These make calls out to the database layer and then return results to the view. They work fine. When I noticed they didn't have [HttpGet] on them, I added them and then the calls stopped working. I can't figure out why, or the rhyme and reason for when they have to be there.
Here's a call I'm making from the view:
function getExcelExport() {
var activePane = $('div.tab-pane.active');
var agencyCompany = $(activePane).find('#Agency_AgencyId').val();
if (!$(activePane).find('#form0').valid()) { return false; }
var month = $(activePane).find('#CommissionMonth').val();
var year = $(activePane).find('#CommissionYear').val();
window.location = 'AgencyManagement/GetCommissionsExcel?agencyID=' + agencyCompany + '&month=' + month + '&year=' + year;
};
and here's the action in the controller:
public ActionResult GetCommissionsExcel(string agencyid, string month, string year)
{
try
{
var as400rep = new iSeriesRepository(new iSeriesContext());
var results = as400rep.GetCommissionExcel(agencyid, month, year);
string xml = String.Empty;
XmlDocument xmlDoc = new XmlDocument();
XmlSerializer xmlSerializer = new XmlSerializer(results.GetType());
using (System.IO.MemoryStream xmlStream = new System.IO.MemoryStream())
{
xmlSerializer.Serialize(xmlStream, results);
xmlStream.Position = 0;
xmlDoc.Load(xmlStream);
xml = xmlDoc.InnerXml;
}
var fName = string.Format("CommissionsExcelExport-{0}", DateTime.Now.ToString("s"));
fName = fName + ".xls";
byte[] fileContents = System.Text.Encoding.UTF8.GetBytes(xml);
return File(fileContents, "application/vnd.ms-excel", fName);
}
catch (Exception ex)
{
Log.Error(ex.Message, ex.InnerException);
throw;
}
}
Is there a simple explanation for this?
The reason that when you added [HttpGet] and the calls 'stopped' working is because you would be calling the method using a different HTTP verb e.g. POST.
Applying a Http verb attribute on a method means, to restrict an action method so that the method handles only HTTP GET requests.
The reason why it all worked when you did not use a http verb attribute is because that action method is then available via all Http verbs.
Mark that Action Method with [HttpPost] and it will work.
[HttpPost]
public ActionResult Action(int id)
{
}
You are able to have the same Method name for a GET and a POST, but the method requires a different signature (overloading).
[HttpGet]
public ActionResult Action() { }
[HttpPost]
public ActionResult Action(int id) { }
This is usually used in a PRG pattern (POST, Redirect, GET). You can read further about this here
[HttpGet] marks the Action as application only for GET requests -
Consider the following:
public ActionResult DoSomething() { }
if you were to GET to /DoSomething OR POST /DoSomething - the action would be invoked.
Specifying:
[HttpGet]
public ActionResult DoSomething() { }
ensures this is only going to be called if the request was a GET
Related
I have an Openlayer's map interface where I'm capturing the user's adding new points to the map. What I want is to take those location data points and save them do a database. So I have a working function on the .cshtml page that looks like this:
map.on('dblclick', function (evt) {
var coordinate = evt.coordinate;
var datapoints = new Array();
var features = source.getFeatures();
for (var i = 0; i < features.length; i++) {
var poi = features[i];
var datapt = new Object();
datapt.X = poi.values_.geometry.flatCoordinates[0];
datapt.Y = poi.values_.geometry.flatCoordinates[1];
datapoints.push(datapt);
}
var xhr = new XMLHttpRequest();
xhr.open("POST", "Draw_Features", true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(datapoints));
});
This seems to work just fine and is sending back an encoded JSON of all the locations. In my Controller file, I have the following:
[HttpGet]
public IActionResult Draw_Features()
{
return View();
}
[HttpPost]
public IActionResult Draw_Features(string someValue)
{
//TODO
return View("Index");
}
[HttpPost]
public IActionResult AddPointsToDB(string someValue)
{
//TODO
return View("Index");
}
I have two problems:
1) I want to return the data to the "AddPointsToDB()" function but it is instead going to the "Draw_Features()" one instead. How do I specify in the xhr.send() the correct landing function?
2) I was expecting the function to receive the JSON via the 'string someValue' variable. But that variable comes in NULL. What is the correct way to access that JSON from within the function?
Thanks!
EDIT: Fixed the JSON convert code which was bombing. Still the same questions...
EDIT 2: Showing POST data from Chrome
For a working demo, follow steps below:
Define a new model to receive the data instead of string input. For passing datapoints to controller action, it will be serialize and de-serialize by MVC Built-in function.
public class Coordinate
{
public double X { get; set; }
public double Y { get; set; }
}
Change Action with [FromBody]IList<Coordinate> coordinates which will specify the modele binding to read the model from body.
public IActionResult AddPointsToDB([FromBody]IList<Coordinate> coordinates)
{
//TODO
return View("Index");
}
As already point out, make sure you set the xhr.open("POST", "AddPointsToDB", true); with the action name you want.
I am creating an MVC 5 application. I am using Rotativa to generate PDFs
they have a method called
public ActionAsPdf(string action, object routeValues);
I am having trouble to direct to POST method of an action
this is that GET and POST actions
[HttpGet]
[ValidateInput(false)]
public ActionResult Create_Brochure(IEnumerable<ProductsPropertiesVM> model)
{
.............
return View(selectedIDs);
}
[HttpPost]
[ValidateInput(false)]
public ActionResult Create_Brochure(string m)
{
return View();
}
Once I run this program its directing to GET method but I want to direct to POST action
using following method
public ActionResult PrintIndex()
{
return new ActionAsPdf("Create_Brochure") { FileName = "Test.pdf" };
}
You need to match the parameters of the POST version of Create_Brochure:
return new ActionAsPdf("Create_Brochure", new List<ProductsPropertiesVM>())
{
FileName = "Test.pdf"
};
Of course, you'll have to pass the correct model data instead of the List<ProductsPropertiesVM>.
mvc - First "get" result for request.params get exception, second "get" is ok.
i have a page that has a field with xml text in it.
i am using validation=false.
but, on post method in the controller i am trying to get the requset.params and i get an error of
"A potentially dangerous Request.Form value was detected from the client". after some digging and debugging i see that first time i am trying to get the request.params i get an exception, BUT when i try to get it for the second time everything is ok.
this is the filter i am using to avoid problems with the xml (i am converting it to binary data and empty the xml string field):
public class RestAPIAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
((SimulationModel)filterContext.ActionParameters["model"]).Data = CommonConverters.StringToByteArray(((SimulationModel)filterContext.ActionParameters["model"]).StringData);
((SimulationModel)filterContext.ActionParameters["model"]).StringData = string.Empty;
base.OnActionExecuting(filterContext);
}
}
and this is the post method:
[HttpPost]
[ValidateInput(false)]
[RestAPIAttribute]
public ActionResult EditSimulation(Guid id, SimulationModel model)
{
try
{
model.RelationModel = new RelationModel(false, this.Resource("Simulations.AddToObjects"), "SimulationToObjects", id, sessionId, Request.Params, new List<ObjectTypes>() { ObjectTypes.Entity, ObjectTypes.EntityType, ObjectTypes.Universe });
}
catch (Exception ex)
{
Logger.LogException(ex);
}
/* more code here*/
return View(newModel);
}
now, as you can see one of the RelationModel's constuctor has a parameter of request.param which is giving me the problem.
my currenty workaround for this issue is just calling it twice (but i am looking for better solution or at least an explanation):
[HttpPost]
[ValidateInput(false)]
[RestAPIAttribute]
public ActionResult EditSimulation(Guid id, SimulationModel model)
{
try
{
try
{
model.RelationModel = new RelationModel(false, this.Resource("Simulations.AddToObjects"), "SimulationToObjects", id, sessionId, Request.Params, new List<ObjectTypes>() { ObjectTypes.Entity, ObjectTypes.EntityType, ObjectTypes.Universe });
}
catch
{
model.RelationModel = new RelationModel(false, this.Resource("Simulations.AddToObjects"), "SimulationToObjects", id, sessionId, Request.Params, new List<ObjectTypes>() { ObjectTypes.Entity, ObjectTypes.EntityType, ObjectTypes.Universe });
}
}
catch (Exception ex)
{
Logger.LogException(ex);
}
/* more code here*/
return View(newModel);
}
If this is ASP.NET MVC 3+ you could use the [AllowHtml] attribute on your model property that contains the XML. This will disable request validation only for this property and not the entire request:
public class SimulationModel
{
[AllowHtml]
public string StringData { get; set; }
}
and then:
[HttpPost]
public ActionResult EditSimulation(Guid id, SimulationModel model)
{
// you could directly use model.StringData here without any
// encoding needed
return View(newModel);
}
And if this is an ASP.NET MVC 2 application running in ASP.NET 4.0 in addition to decorating your controller action with the [ValidateInput(false)] attribute you might need to put the following in your web.config:
<httpRuntime requestValidationMode="2.0"/>
If it is an ASP.NET MVC 3 application you don't need to put this line in your web.config in order to use the [ValidateInput(false)] attribute.
How can i prevent a partial view from being loaded by typing http://mydomain.com/site/edit/1 Is there any way of doing this?
/Martin
If you load your partials through Ajax then you can check if the request HTTP header HTTP_X_REQUESTED_WITH is present and its value is equals to XMLHttpRequest.
When a request is made through the browser that header is not present
Here is a very simple implementation of an Action Filter attribute that does the job for you
public class CheckAjaxRequestAttribute : ActionFilterAttribute
{
private const string AJAX_HEADER = "X-Requested-With";
public override void OnActionExecuting( ActionExecutingContext filterContext ) {
bool isAjaxRequest = filterContext.HttpContext.Request.Headers[AJAX_HEADER] != null;
if ( !isAjaxRequest ) {
filterContext.Result = new ViewResult { ViewName = "Unauthorized" };
}
}
}
You can use it to decorate any action where you want to check if the request is an ajax request
[HttpGet]
[CheckAjaxRequest]
public virtual ActionResult ListCustomers() {
}
I believe the [ChildActionOnly] attribute is what you're looking for.
[ChildActionOnly]
public ActionResult Edit( int? id )
{
var item = _service.GetItem(id ?? 0);
return PartialView( new EditModel(item) )
}
Phil Haack has an article using it here
I have a ProductController with actions Index (which Loads a blank form). The form also posts to itself as its a complex form and the form elements like dropdowns show posted values
the code is as follows
public ActionResult Index()
{
int id;
id = Convert.ToInt32(Request.Form["ddlLendingType"]);
if (id == 0)
id = 1;
ProductCommonViewModel viewData = new ProductCommonViewModel(_prodRepository.Method1(),_prodRepository.Method2())
return View(viewData);
}
When I click submit from the form, it saves the product and if it fails it should show the validation error messages.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Save(FormCollection fc)
{
Product product = new Product();
try
{
...fill out all properties from form collection
_prodRepository.SaveProduct(product);
return RedirectToAction("Index", "Product");
}
catch (Exception ex)
{
TempData["Message"] = "An Error Occured while saving the product!";
Validation.UpdateModelStateWithRuleViolation(product, ViewData.ModelState);
// WHEN I call redirect to action Index on this view I can see the TempData variable but I cannot see validation summary and individual validation messages.How do I persist the msgs across requests?
}
}
The helper method definition is as follows:
public static void UpdateModelStateWithRuleViolation(IRuleEntity entity, ModelStateDictionary dictModel)
{
List<RuleViolation> violations = entity.GetRuleViolations();
foreach (var item in violations)
{
dictModel.AddModelError(item.PropertyName, item.ErrorMessage);
}
}
Pass modelstate into tempdata too.
Btw, instead of this:
public ActionResult Index()
{
int id; //and here You could join declaration with assignment
id = Convert.ToInt32(Request.Form["ddlLendingType"]);
You can do this:
public ActionResult Index(int ddlLendingType)
{
And using FormCollection is a bad practice which should not be used. For extreme cases - create custom model binder (CodeCampServer has quite nice binding mechanism) or action filter (Kigg`s source).
I had a problem with preserving TempData across multiple requests, I did the following, to refresh the TempData for every redirect action:
protected override RedirectToRouteResult RedirectToAction(string actionName,
string controllerName, System.Web.Routing.RouteValueDictionary routeValues)
{
TempData["Notice"] = TempData["Notice"];
TempData["Error"] = TempData["Error"];
TempData["Warning"] = TempData["Warning"];
return base.RedirectToAction(actionName, controllerName, routeValues);
}