Given the following two urls:
/employee/list/active
/employee/list/inactive
How do I map the active/inactive part of the url to a boolean action method parameter, active being true, inactive being false?
[Route("employee/list")]
public ActionResult List(bool? active = null)
The enum is a correct approach as it allows you to easily add new statuses in the future :
[Route("employee/list/{status}")]
public ActionResult List(status status)
{
...
}
public enum status { active, inactive }
Even though, based on the single responsibility principle, I would prefer a simpler solution like this:
[Route("employee/list/active")]
public ActionResult ListActive()
{
return List(true);
}
[Route("employee/list/inactive")]
public ActionResult ListInactive()
{
return List(false);
}
public ActionResult List(status status)
{
...
}
I reworked it to use a string so it worked like this:
[Route("employee/list")]
[Route("employee/list/{active}")]
public ActionResult List(string active ="both")
{
///Stuff happens here
}
It's important to add the first, parameterless route if you need the parameter to be optional.
Update: This route works too
[Route("employee/list/{active='both'}")]
Related
in Asp.Net MVC if I decorate an action method with attribute NonAction then it wont be allowed to be called by the user visiting the site.
same happens when I make it private
So whats the difference between the two and is there a special purpose for which NonAction attribute has been made?
For example whats the difference between
[NonAction]
public ActionResult SomeAction(){}
And
private ActionResult SomeAction(){}
in the context of asp.net MVC of course I know one is public and the other one is private
That's the only difference. The attribute is used when you want a method that has a signature that would make it an action, but that you don't want to be an action.
An example for a use for that is a method that action methods call to produce the ActionResult for them:
[NonAction]
public JsonResult JsonInfo(string id, string value) {
return Json(new { id = id, value = value });
}
public JsonResult GetBusInfo() {
return JsonInfo("4", "Bus");
}
public JsonResult GetCarInfo() {
return JsonInfo("8", "Car");
}
The reason to make it public instead of private would be so that actions in other controllers could also use it.
Both works same with action method,you can use them seperately or together.
[NonAction]
private ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
FEED_TBL fEED_TBL = db.FEED_TBL.Find(id);
if (fEED_TBL == null)
{
return HttpNotFound();
}
return View(fEED_TBL);
}
If declare it like the above code then when we will try to go to details action method it will not go to it.Rather it will show the error.
{{ HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.}}
This shows that our detail link on view does found any reference to details action method and our controller to.
With Web API, I need to redirect the following two Restful routes to two different action methods:
/products/2 -> Get info for product id 2
/products?someOptionalId=456 -> Get info for all products. Use someOptionalId as a filter if provided.
Unfortunately using the standard routing and model binding scheme, since both URLs point to the same products controller and have one id as a parameter, I either run into a compile time issue creating two Get methods with same int parameter, or a run time issue with MVC not able to pick a particular action method
Compile time error
public IQueryable<Product> Get(int someOptionalIdQs)
{
}
public Product Get(int id)
{
}
Run time error (Note hack to use a string for someOptionalIdQs and then convert to int)
public IQueryable<Product> Get(string someOptionalIdQs)
{
}
public Product Get(int id)
{
}
Please suggest a fix ideally without having to make any routing config changes given that I would like to keep the routing as clean as possible. Thanks.
As your Method has an optional Id parameter you can simply use a nullable int for the Get for the collection.
The code below will support the following urls:
http:// server /api/products
http:// server /api/products?someOptionalIdQs=3
http:// server /api/products/2
Code example
public class Product
{
public string Name { get; set; }
}
public class ProductsController : ApiController
{
public IQueryable<Product> Get([FromUri] int? someOptionalIdQs = null)
{
if(someOptionalIdQs.HasValue)
{
//apply the filter
}
return new List<Product>().AsQueryable();
}
public Product Get(int id)
{
return new Product();
}
}
Use your first approach but try renaming one of your Get methods. Note that if the action name doesn't have a prefix of 'Get', make sure the [HttpGet] attribute is used.
// [HttpGet]
public IQueryable<Product> Get2(int someOptionalIdQs)
{
}
public Product Get(int id)
{
}
What you can do it probly see if the this.HttpContext.Request.QueryString.AllKeys.Contains("someOptionalIdQs") then do the processing according to Option Id Qs else your normal work flow would work.
But this would be a cryptic implementation an ideally you should create a new URL all together for a different work flow for the app.
I’m trying to call a action method from different controller but it´s not working. It simply skips the RedirectToAction
here's my code:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
return RedirectToAction("Insert", "Policies", new { tempPolicy = TempPolicy });
}
Please help.
You cannot send complex objects when redirecting. You will have to include each property as query string parameter. And this works only with simply scalar properties.
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
return RedirectToAction("Insert", "Policies", new
{
id = TempPolicy.Id,
prop1 = TempPolicy.Prop1,
prop2 = TempPolicy.Prop2,
...
});
}
If you have complex properties you will have to include them as well so that the default model binder is able to bind the model in the target action from the query string parameters:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
return RedirectToAction("Insert", "Policies", new RouteValueDictionary
{
{ "id", TempPolicy.Id },
{ "prop1", TempPolicy.Prop1 },
{ "prop2", TempPolicy.Prop2 },
{ "prop3.subprop1", TempPolicy.Prop3.SubProp1 },
{ "prop3.subprop2", TempPolicy.Prop3.SubProp2 },
...
});
}
and your target action:
public ActionResult Insert(TempPoliciesUpload TempPolicy)
{
...
}
Another possibility is to persist this object in your backend before redirecting and then pass only the id:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
int id = Repository.Save(TempPolicy);
return RedirectToAction("Insert", "Policies", new { id = id });
}
and in your target action:
public ActionResult Insert(int id)
{
TempPoliciesUpload TempPolicy = Repository.Get(id);
...
}
I hope you have
public ActionResult Insert(TempPoliciesUpload tempPolicy)
action method in PoliciesController class.
Please see the overload of RedirectToAction here
Remove the parameter from the controller you're redirecting to and remove new { tempPolicy = TempPolicy }. See if that works (and then you localized the problem to parameter).
Most likely you need to cast it to the type of the action you redirecting to (hence Mark asked you for that signature) or play with it otherwise, maybe put in quotes (I doubt but good to try)
If even that doesn't work, check your spellings (this is why I do not like magic strings and love T4MVC) - but I doubt that either, naming looks correct.
Another likely possibility is something that solved for others here: RedirectToAction not working
Has anyone tried the first solution with complex objects?
I mean this one:
"...and your target action:..."
public ActionResult Insert(TempPoliciesUpload TempPolicy)
{
...
}
I don't think a RouteValueDictionary will convert or cast into a complex object. (Serialization must be used, I think)
My solution was passing the parameters as a RouteValueDictionary and receiving each parameters individually in the target action.
If you need to send a complex object you can try just returning the view from another controller like this:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
return View("~Views/Policies/Insert.cshtml", TempPolicy );
}
If you want this view to post back to the correct controller method you will need to specify this in the 'BeginForm' Html Helper:
...
#using(Html.BeginForm("Insert", "Policy")
{
...
}
This really isn't best practice, but it is a workaround that you can use until you fix enough of the rest of your app so that you can use the redirects properly.
For decoupling, #Darin Dimitrov answer would be suited best. However if you do not wish to pass details in the URL, so that for example the user cannot fiddle with the data, you can use the short-lived persistence TempData feature like this:
public ActionResult sendPolicy(TempPoliciesUpload TempPolicy)
{
TempData["Policy"] = TempPolicy;
return RedirectToAction("Insert", "Policies");
}
Then retrieve it in the Insert:
public ActionResult Insert()
{
TempPoliciesUpload TempPolicy = (TempPoliciesUpload)TempData["Policy"];
}
Below, in CreateTest, uponsuccessful, I want to redirect to Tests from CreateTest.
I want to do something like the following:
public ActionResult Tests(int ID, string projectName)
{
TestModel model = new TestModel (ID, projectName);
return View(model);
}
[HttpPost]
public ActionResult CreateTest(TestModel model)
{
try
{
return RedirectToAction("Tests");
}
catch (Exception e)
{
ModelState.AddModelError("Error", e.Message);
return View(model);
}
}
You might need to provide the arguments when redirecting:
return RedirectToAction("Tests", new {
ID = model.ID,
projectName = model.ProjectName
});
and the url you will be redirecting to will now look something like this:
/Foo/Tests?ID=123&projectName=abc
I know this is a bit old but...
What I've done in the past is have a "MessageArea" class exposed as a property on my base controller that all my controllers ultimately inherit from. The property actually stores the class instance in TempData. The MessageArea has a method to Add() which takes a string message and an enum Type (e.g. Success, Error, Warning, Information).
I then have a partial that renders whatever messages are in MessageArea with appropriate styling according to the type of the message.
I have a HTMLHelper extension method RenderMessageArea() so in any view I can simple say #Html.RenderMessageArea(), the method and partial take care of nulls and nothing is output if there are no messages.
Because data stored in TempData only survives 1 request it is ideal for cases where you want your action to redirect but have 1 or more messages shown on the destination page, e.g. an error, not authorised page etc... Or if you add an item but then return to the index list page.
Obviously you could implement something similar to pass other data. Ultimately I'd say this is a better solution to the original question than the accepted answer.
EDIT, EXAMPLE:
public class MessageAreaModel {
public MessageAreaModel() {
Messages = new List<Message>();
}
public List<Message> Messages { get; private set; }
public static void AddMessage(string text, MessageIcon icon, TempDatadictionary tempData) {
AddMessage(new Message(icon, text), tempData);
}
public static void AddMessage(Message message, TempDataDictionary tempData) {
var msgArea = GetAreaModelOrNew(tempData);
msgArea.Messages.Add(message);
tempData[TempDataKey] = msgArea;
}
private static MessageAreaModel GetAreaModelOrNew(TempDataDictionary tempData) {
return tempData[TempDataKey] as MessageAreaModel ?? new MessageAreaModel();
}
The above class can then be used to add messages from your UI layer used by the controllers.
Then add an HtmlHelper extension like so:
public static void RenderMessageArea(this HtmlHelper html) {
html.RenderPartial("MessageArea",
(MessageAreaModel)html.ViewContext.TempData[MessageAreaModel.TempDataKey] ?? MessageAreaModel.Empty);
html.ViewContext.TempData.Remove(MessageAreaModel.TempDataKey);
}
The above is not fully completed code there are various bells and whistles I've left out but you get the impression.
Make the int nullable:
public ActionResult Tests(int? ID, string projectName){
//...
}
Image the following controller method:
public ActionResult ShipmentDetails(Order order)
{
return View(new OrderViewModel { Order = order });
}
The incoming order parameter is filled from a custom model binder, that either creates a new order for this session and stores it in the session, or reuses an existing order from the current session. This order instace is now used to fill a shipment details form, where users can enter their address and so on.
When using #using(Html.BeginForm()) in the view. I cannot use the same signature for the post method again (because this would result in ambigious method names) and I found me adding a dummy parameter just to make this work.
[HttpPost]
public ActionResult ShipmentDetails(Order order, object dummy)
{
if (!ModelState.IsValid)
return RedirectToAction("ShipmentDetails");
return RedirectToAction("Initialize", order.PaymentProcessorTyped + "Checkout");
}
What are the best practices for this? Would you simply rename the method to something like PostShipmentDetails() and use one of the overloads of BeginForm? Or does the problem originate from the point, that the first method has the order parameter?
You could use the ActionName attribuite:
[HttpPost]
[ActionName("ShipmentDetails")]
public ActionResult UpdateShipmentDetails(Order order) { ... }
or a more classic pattern:
public ActionResult ShipmentDetails(int orderId)
{
var order = Repository.GetOrder(orderId);
return View(new OrderViewModel { Order = order });
}
[HttpPost]
public ActionResult ShipmentDetails(Order order)
{
if (!ModelState.IsValid)
return RedirectToAction("ShipmentDetails");
return RedirectToAction("Initialize", order.PaymentProcessorTyped + "Checkout");
}