I have following code:
public IActionResult Index()
{
_fileModel = new FileModel { Files = _fileService.GetFilesForArchivation(), IdOfService = default(Guid) };
return View(_fileModel);
}
and Iv'e implemented two methods also :
public IActionResult Compress()
{
//_compressingService.CompressAllFiles();
_fileModel.IdOfService = _compressResolverService.Start(_serviceProvider);
return RedirectToAction("Index");
}
public IActionResult CancelCompressing()
{
//_compressingService.Stop();
_compressResolverService.Stop(_fileModel.IdOfService);
return RedirectToAction("Index");
}
In first method Iv'e added IdOfService to FileModel. In second code I am trying to send this field as a parameter. In first method i get normal value of guid but in second method it has default value and i get
NullReferenceException
Kindly review this issue and give feedback.
If I understand correctly from one action to another you are losing your value, you can use
TempData["IdOfService"] = _fileModel.IdOfService;
TempData.Keep();
To keep this in "memory".
Related
In my controller.Post I am using a link generator to return the endpoint of the newly added resource to the user. The link generator always returns null and I cant quite put my finger on why?
I included controller code that is adding to the database. Note the line calling _linkGenerator.GetPathByAction - the parameters passed in are (in order) 1. the action (in this case the name of the method that does the 'Get'), 2, the controller name and 3 an anonymous type containing the correct Id. I honestly cant see a problem with this.
how the controller name is defined :
namespace CarPriceComparison.Controllers.Api{
[Route("api/vehicles")]
[ApiController]
public class VehicleController : Controller
'Get' Action - function header :-
[HttpGet("{vehicleId_:int}")]
public IActionResult GetVehicle(int vehicleId_)
{
'problem' code that does the get. To me, the parameters passed into _linkGenerator.GetPathByAction look perfectly fine. Suggestions as to why I am always returned a null value?
[HttpPost("")]
public async Task<IActionResult> PostNewVehicleData(VehicleViewModel vehicleData_)
{
try
{
if (ModelState.IsValid)
{
var newVehicle = _mapper.Map<Vehicle>(vehicleData_);
_vehicleRepository.AddVehicle(newVehicle);
var location = _linkGenerator.GetPathByAction("GetVehicle", "vehicles",
new {vehicleId_ = newVehicle.Id});
if (string.IsNullOrWhiteSpace(location))
{
return BadRequest("could not use vehicleId_ to create a new vehicle in the dataase");
}
if (await _vehicleRepository.SaveChangesAsync())
{
var vehicleModel = _mapper.Map<VehicleViewModel>(newVehicle);
return Created(location, _mapper.Map<VehicleViewModel>(newVehicle));
}
}
return BadRequest("Failed to save the vehicle");
}
catch(Exception ex)
{
return BadRequest($"Exception Thrown : {ex}");
}
}
You have to try this:
var location = _linkGenerator.GetPathByAction("GetVehicle", "Vehicle",
new {vehicleId_ = newVehicle.Id});
Or remane your controller to VehiclesController. Then try this:
var location = _linkGenerator.GetPathByAction("GetVehicle", "Vehicles",
new {vehicleId_ = newVehicle.Id});
I am using MVC 4, and I have the following:
[HttpGet]
public ActionResult SomeForm(modelType model = null)
{
if(model != null)
return View(model);
return View(getModelFromSomewhere());
}
[HttpPost]
public ActionResult SomeForm(modelType model)
{
if(isValid())
doSomething();
else
return SomeForm(model) // Line in Question
}
However, obviously, I am getting an ambiguous method error on "Line in Question". I'm wondering if anyone has an elegant solution to be able to specify to return specifically the [Get] method of the same name?
Thank you!
You can't have methods with the same signature as you've pointed out already. In C# it also means you can't distinguish functions by just return type - so you must use different names if parameters are same (again default values are ignored when matching of signatures).
If you want separate GET and POST handler - use different names of methods and ActionNameAttribute to name the action:
[HttpGet]
[AciontName("SomeForm")]
public ActionResult SomeFormGet(modelType model = null) ...
[HttpPost]
[AciontName("SomeForm")]
public ActionResult SomeFormPost(modelType model) ...
make it compile...
[HttpPost]
public ActionResult SomeForm(modelType model, FormCollection fc)
{
if(isValid())
doSomething();
else
return SomeForm(model) // Line in Question
}
If you are using http get method you are waiting that browser will send you serialized model as a string query. For example, you are waiting url like
http://example.com?name=Andrew&type=Worker&field1=param1&field2=param2&....
It is common practice to use only id in your get method, so you can do it like this:
[HttpGet]
public ActionResult SomeForm(int id)
{
var model = FindModelById(id);
if(model != null)
return View(model);
return View(getModelFromSomewhere());
}
If you are looking for an elegant solution, it will be more elegant in architecture
I have two actions in my controller when a user call one i need to redirect it to another one and pass a complex object :
first action :
public virtual ActionResult Index(string Id) {
var input = new CustomInput();
input.PaymentTypeId = Id;
return RedirectToAction(MVC.Ops.SPS.Actions.Test(input));
}
second action :
public virtual ActionResult Test(CustomInput input) {
return View();
}
The probelm is that the input arrives null at the second action. how can i solve it?
You can solve this using temp data to temporarily hold a value from which the second method retrieves that value.
public virtual ActionResult Index(string Id)
{
var input = new CustomInput();
input.PaymentTypeId = Id;
TempData["TheCustomData"] = input; //temp data, this only sticks around for one "postback"
return RedirectToAction(MVC.Ops.SPS.Actions.Test());
}
public virtual ActionResult Test()
{
CustomInput = TempData["TheCustomData"] as CustomInput;
//now do what you want with Custom Input
return View();
}
You can keep your tempData going so long as it is never null using the .keep() method like this,
if (TempData["TheCustomData"] != null)
TempData.Keep("TheCustomData");
I want to make sure that you know that RedirectToAction creates new HTTP request so this create another GET and all you can pass is RouteValueDictionary object which is like having query string parameters which is list of key and value pairs of string. That said, You can't pass complex object with your way of code however the TempData solution mentioned by #kyleT will work.
My recommendation based on your code is to avoid having two actions and redirect from one another unless you are doing something more than what you have mentioned in your question. Or you can make your Test action accepting the id parameter the your Index action will contain only RedirectToAction passing the id as route parameter.
Edit:
Also, If you have no primitive properties you can pass your object like the following (Thanks to #Stephen Muecke)
return RedirectToAction("Test",(input);
I have an object from a database that is used in a lot of places in my application.
The actual precise object is a bit complicated to build, and, especially during development, I have changed it several times. For this reason, I extracted the method out of the Controller and built a method that has a return type of the object.
However, it was possible that this object did not exist, and if it did not, my code would create it and return it.
For example:
public ActionResult Index()
{
var model = GetTheObject();
return View(model);
}
public MyComplicatedObject GetTheObject()
{
MyComplicatedObject passback = ...database query....
if(passback==null)
create the object here....
return passback;
}
However, I no longer want to create a default object. If it does not exist, I want the user to be sent to a view to create a new one.
I don't know if it is because I am coding at almost 4AM, or if I am just not that good, but, a neat way of doing this is escaping me.
I know the following won't work, but ideally this is what I want:
public MyComplicatedObject GetTheObject()
{
MyComplicatedObject passback = ...database query....
if(passback==null)
return View("CreateObject");
return passback;
}
Obviously though, this will not work.
The best solution I can think of is to basically return either null or an exception, then have if(passback==null)&return View("CreateObject"); (in case of a null) on the ActionResult.
However, as I want to repeat this in a few places, it makes more sense to be able to just have GetTheObject() in one line/call from the ActionResult and nothing else.
Is there any way to achieve this?
I have a similar scenario where I want to return a "NotFound" view in case that my repository returns a null object. I implemented a ViewForModel helper method to avoid repeating myself:
public ActionResult Details(int id)
{
var model = _repository.Retrieve(id);
return ViewForModel("Details", model);
}
public ActionResult Edit(int id)
{
var model = _repository.Retrieve(id);
return ViewForModel("Edit", model);
}
private ActionResult ViewForModel(string viewName, object model)
{
return model == null
? View("NotFound")
: View(viewName);
}
Just return null from your method, and have your action method return the create view when it gets a null.
I can't get my Index() action to Pass a valid model to my Review() Action
... ActionResult Index()...
else
{
return RedirectToAction("Review", wizard); <--wizard is a valid object here....
}
ActionResult Review()
public ActionResult Review()
{
return View(_wizard); <-- THis is always null.
}
Update:
Here is my whole controller. I want to take the user from the wizard index, to a review page, then finally to a transmit page that actually saves the data. I'm having real problems wrapping my head around the final piece. When you are used to asp classic where you have to explicitly write everything from scratch, it's kind of hard to get used to the Magic inherit in MVC3. So, I bet I'm writing a lot of uneeded code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using mvc3test.Models;
using Microsoft.Web.Mvc;
using System.Web.Mvc;
using mvc3test.Services;
namespace mvc3test.Controllers
{
public class WizardController : Controller
{
private WizardViewModel wizard = new WizardViewModel();
private DR405DBContext db;
public WizardController(IDBContext dbContext)
{
db = (DR405DBContext)dbContext;
}
public WizardController()
{
db = new DR405DBContext();
}
public ActionResult Index()
{
wizard.Initialize();
return View(wizard);
}
[HttpPost]
public ActionResult Index([Deserialize] WizardViewModel wizard, IStepViewModel step)
{
wizard.Steps[wizard.CurrentStepIndex] = step;
if (ModelState.IsValid)
{
if (!string.IsNullOrEmpty(Request["next"]))
{
wizard.CurrentStepIndex++;
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
wizard.CurrentStepIndex--;
}
else
{
return View("Review", wizard);
}
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
wizard.CurrentStepIndex--;
}
return View(wizard);
}
[AllowAnonymous]
public ActionResult Review(WizardViewModel model)
{
return View(model);
}
[AllowAnonymous]
[HttpGet]
public ActionResult Review(Int32 ID)
{
var service = new DR405Service(db);
var myWizard = service.WireUpDataModelToViewModel(service.DBContext.dr405s.Single(p => p.ID == ID));
return View(myWizard);
}
public ActionResult Transmit()
{
var service = new DR405Service(db);
service.Wizard = wizard;
service.Save();
return View();
}
}
}
Per msdn RedirectToAction will cause another get request to the Review action.
Returns an HTTP 302 response to the
browser, which causes the browser to
make a GET request to the specified
action.
This causes the wizard object to loose its value and needs to be repopulated.
View() simply returns the view associated with that action within the current context.
You could either place wizard in TempData, return View("Review", wizard), or have wizard passed as route values if possible.
RedirectToAction returns an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action. So you can't pass a complex object as you do
It's not the best solution but try to put wizard object before redirecting in ViewData :
ViewData["wizard"] = wizard
and then get it in Review()
var wizard = (Wizard)ViewData["wizard"];
return RedirectToAction("Review", wizard); passed the wizard object to the view named Review. Review will need to be a strongly typed view based on the same class as wizard.
Posting your view code would be helpful if this does not answer your question.