Propagating QueryString parameter in RedirectToAction calls - asp.net

I want to make sure that a particular parameter in the QueryString, in my case the request_id is propagated to the redirected action.
Say for example, I have an Action First,
[HttpPost]
public ActionResult First()
{
////////////////////
// Lots of code ...
////////////////////
return RedirectToAction("Second");
}
Now say, the First postback had a parameter in the QueryString, which I would like to pass to the Second action. One way to do it would be to pass the value in the RedirectToAction call itself,
string requestId = Request.QueryString[REQUEST_ID_KEY];
return RedirectToAction("Second", new { REQUEST_ID_KEY = requestId });
But I have to do this in a series of Actions and I am unwilling to incorporate request id propagation logic inside the action. It would be better if I could incorporate this inside an ActionFilter, but I cant figure out how to add parameters to the QueryString from an ActionFilter. Any ideas?

public class PreserveQueryStringAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var redirectResult = filterContext.Result as RedirectToRouteResult;
if (redirectResult == null)
{
return;
}
var query = filterContext.HttpContext.Request.QueryString;
// Remark: here you could decide if you want to propagate all
// query string values or a particular one. In my example I am
// propagating all query string values that are not already part of
// the route values
foreach (string key in query.Keys)
{
if (!redirectResult.RouteValues.ContainsKey(key))
{
redirectResult.RouteValues.Add(key, query[key]);
}
}
}
}
and then:
[HttpPost]
[PreserveQueryString]
public ActionResult First()
{
////////////////////
// Lots of code ...
////////////////////
return RedirectToAction("Second");
}

If you need it in subsequent action than please add it that param in Session or TempData (But need to re-assign in each action) so you dont need to pass it as a querystring in each action. In case of session, once you done with all actions than remove that key from the Session.

Here is a blogpost I wrote on how to fluently add querystring parameters in the action

Related

Audit.NET CustomFields keeping value between requests

I tried to use these two ways to write a customfield and it is recording correctly, but it is keeping customfields between requests
public class LoggerAudit : ILoggerAudit
{
public void AddOnSavingAction(string key, object value)
{
Configuration.AddOnSavingAction(scope =>
{
scope.SetCustomField(key, value);
//scope.Event.CustomFields.Remove(key);
//scope.Event.CustomFields.Add(key, value);
});
}
}
For example:
In the first request my webapi recorded the customfield 'field-A', but in the second request my webapi, there was no need to write this customfield, but it was kept in scope and consequently in my json
I tried this setting, but it didn't work
.WithAction(action =>
{
action.OnEventSaved(scope => scope.Event.CustomFields = new Dictionary<string, object>());
});
The custom actions attached with AddOnSavingAction / OnEventSaved are globally attached and will execute for each and all the events before saving or after saving occurs (respectively), so you should attach each action just once.
But your use case looks like you don't have a way to derive the custom field value from the audit scope, so a custom action will not be useful.
Also I guess you are using Audit.WebApi extension. If that's the case, you won't need a custom action to add a custom field, since you can access the AuditScope with the provided extension methods directly on your controllers or in any place where you can get the current HttpContext, for example:
using Audit.WebApi;
[AuditApi]
public class UsersController : Controller
{
public IHttpActionResult Get(string id)
{
//...
var auditScope = this.GetCurrentAuditScope();
auditScope.SetCustomField("MyField", Guid.NewGuid());
//...
}
}
or just
private void SetCustomField(HttpContext context, string key, object value)
{
var auditScope = context.GetCurrentAuditScope();
auditScope.SetCustomField(key, value);
}

Remember Values Asp.Net

This is my controller code:
private string testVal;
public ActionResult Index()
{
testVal = "test";
return View();
}
public ActionResult NextView()
{
if (testVal == null)
Debug.WriteLine("testVal is null");
return View();
}
Is it possible to remeber values like testVal after changing page? It seems that when redirecting it resets values (testVal in NextVal is null).
Edit:
I try to save values to session but Session is null. I am using SignalR and when user is connected to page i use static event from hub to inform controller that user has connected - but inside method that runs on that event Session is unfortunetly null.
My controller code:
public ActionResult Index()
{
LoadingHub.userConnected += new EventHandler<IdEventArgs>(UserConnected);
return View();
}
private void UserConnected(object sender, IdEventArgs e)
{
Debug.WriteLine("User Connected with Id: " + e.Id);
if (Session == null)
Debug.WriteLine("Session is null");
}
My signalr hub:
public class LoadingHub : Hub
{
public static event EventHandler<IdEventArgs> userConnected;
//Function informs server that user has connected
public void Connected()
{
Debug.WriteLine("Hub Connected Method");
var id = Context.ConnectionId;
userConnected(this, new IdEventArgs(id));
}
}
Every time that you make a request a new instance of the controller is created so using a private field you will not be able to retain the value of this variable.
The easiest way for you to retain it it is to use a session. (if you want to retain this value per user base)
for example in your code
public ActionResult Index()
{
System.Web.HttpContext.Current.Session["testVal"] = "test";
return View();
}
public ActionResult NextView()
{
if (System.Web.HttpContext.Current.Session["testVal"] == null)
Debug.WriteLine("testVal is null");
return View();
}
you can use cookie or cache to replace the variable.
when you redirect to a webpage ,the controller will be newed ,so you cannot get the right testVal .but the cookie is stored in broswer .so you can set it and get .
You may use session or Pass the data to the controller
Have you looked into ASP.NET server side state management click here.
These are basically different ways to remember a value on the server once a new page has been loaded.
So a few server side techniques you could use to remember testVal are Session State or Application State. However Session State is more suitable for your scenario as it is only specific to the user's current session whereas Application State stores data that can be shared between sessions and would therefore be more ideal for global variables.
You can read the link I provided to read more on the differences though.
I would also like to warn you (as some say to use cookies), the user can delete or disable or manipulate them on the browser so this isn't an ideal solution.

Get Action parameters from mvc

I'm trying to get the parameters of a request on exception and log them. How can I get action parameters from a request if the parameter is an object in asp.net mvc? I can only get the parameters if I send in the parameters like this:
public virtual ActionResult TestAction(string A,string B){
}
But not like this:
public virtual ActionResult TestAction(ObjectQuery query){
}
My code:
protected override void OnException(ExceptionContext filterContext)
{
string action = filterContext.RouteData.Values["action"].ToString();
string controller = filterContext.RouteData.Values["controller"].ToString();
string parameters = filterContext.HttpContext.Request.QueryString
}
EDIT:
QueryString returns empty:
You should use reflection to achieve this:
var parameters = MethodBase.GetCurrentMethod().GetParameters();
foreach (ParameterInfo param in parameters)
{
//do your logging here
}
I maybe over simplifying it, but you can get the parameters passed to the action using the Request.Form or Request.QueryString. The nice thing about them is they are of the type NameValueCollection which makes it easy to merge them together.
Then the exception is being thrown on the respective controller so you can get the method signature via reflection, albeit without the named parameter.
Using the simple code below properly gave the parameters passed to the action and doesn't care if it is a POST or GET operation.
protected override void OnException(ExceptionContext filterContext)
{
//Get all the info we need to define where the error occured and with what data
var param = new NameValueCollection {Request.Form, Request.QueryString};
var controller = filterContext.RouteData.Values["controller"].ToString();
var action = filterContext.RouteData.Values["action"].ToString();
var signature = filterContext.Controller.GetType().GetMethod(action).ToString();
//Let's write it to the stream in this case
Response.Write(string.Format("<h3>Controller: {0}</h3>", controller));
Response.Write(string.Format("<h3>Action: {0}</h3>", action));
Response.Write(string.Format("<h4>Signature: {0}</h4>", signature));
foreach (var key in param.AllKeys)
{
Response.Write(string.Format("<strong>Key:</strong> {0} = {1}<br />", key, param[key]));
}
Response.End();
}
There are some downsides I would imagine though. Collections will probably be represented in a very unfriendly way. Data which wasn't supposed to bind with the specific model will be shown as well.

ASP.NET MVC - Object reference not set to an instance of an object

I have recently taken over support of an ASP.NET MVC project, and am trying to work through some of the errors, one in particular has me stumped though.
We have a 'New' page to add new items, with the following code running when the page is posted:
[HttpPost]
public ActionResult New(RecordView i)
{
if(ModelState.IsValid)
{
repository.AddRecord(i.DogIncident);
return View("Index");
}
return View("Index");
}
However, when it tries to load the Index view, I get the following error: "Object reference not set to an instance of an object." and it points to the following block of code at the top of a file called RecordsView.cshtml:
#for (var i = 0; i < Model.Records.Count; i++)
The record does add correctly though, it just doesn't load the listings page correctly, and since this is just a "nice to have" I thought I'd simplify things by changing it so that it either returns some text which generates an error as it's expecting a boolean returned.
Any ideas on how to fix this please? I'm stumped.
Thanks!
The flow of your code here doesn't look right:
[HttpPost]
public ActionResult New(RecordView i)
{
if(ModelState.IsValid)
{
repository.AddRecord(i.DogIncident);
return View("Index");
}
return View("Index");
}
From your description above, it sounds as though you're POSTing from your New view, to this New action, which should then redirect, when successful, to your Index action. Currently, the above code is not doing that, and it also fails to redisplay the form if the model isn't valid. It should probably look more like this:
[HttpPost]
public ActionResult New(RecordView i)
{
if(ModelState.IsValid)
{
repository.AddRecord(i.DogIncident);
return RedirectToAction("Index");
}
// Redisplay the `New` view, passing the model
// back to it in order to repopulate the form
return View(i);
}
The important distinction between return View(...) and return RedirectToAction(...) is that the latter will run the action and return the associated view. The former will simply return the view. That has implications in that if the Index action builds a model, and passes it to the Index view, none of that will happen if you simply return the view.
You could of course do something like:
return View("Index", new YourModelType());
but that isn't going to work if, as discussed above, your Index action performs some other data construction for your model, such as building drop down lists, which new YourModelType() wouldn't do.
Also, when data that is sent to a POST action is valid, you should be redirecting to another action (as I've done above), rather than simply returning a view, in order to conform with the Post-Redirect-Get pattern, which prevents some types of duplicate form submissions.
You display Index view, and seems that it requires some Model - Model.Records. And you don't pass it in this HttpPost action.
If you have action for that Index page, then you can just do
[HttpPost]
public ActionResult New(RecordView i)
{
if(ModelState.IsValid)
{
repository.AddRecord(i.DogIncident);
return RedirectToAction("Index");
}
return View(i);
}
It will just redirect a user to Index view, after creation of new RecordView item
Basically you may are trying to achieve PRG(Post/Redirect/Get) modal.
I guess, the problem is you are not sending the model for your GET request.
Post--> Save --> Redirect --> Load Data -->Assign to View in Index -->Access in view
//POST & REDIRECT
[HttpPost]
public ActionResult New(RecordView i)
{
if(ModelState.IsValid)
{
repository.AddRecord(i.DogIncident);
return RedirectToAction("Index");
}
}
//GET
public ActionResult Index()
{
var model=new MyViewModel();
model.Records=repository.GetRecords(i.DogIncident);
return View(model); //Assign to View in Index
}
Index.cshtml
#model MyViewModel
#for (var i = 0; i < Model.Records.Count; i++)
If Records is a list, make sure your ViewModel has a constructor:
public class RecordView
{
public List<Record> Records { get; set; }
public RecordView()
{
Records = new List<Record>();
}
}
You mentioned that the record adds correctly, so you must be passing a valid record model into your view from some other action than the one provided.
If #for (var i = 0; i < Model.Records.Count; i++) is the cause of this error, my guess is that the model exists, but the Records property has not been set. One immediate work around would be checking the existence of this property before accessing it. For example:
if (Model.Records != null) {
for (var i = 0; i < Model.Records.Count; i++) .....
}
i think you have collections are not instantiated, the error may be in models not in view models. this because when ever you have a collection you need to instantiate inside of constructor of that entity.
May be this is your answer...!Just look
[HttpPost]
public ActionResult New(RecordView)
{
if(ModelState.IsValid)
{
repositry.AddRecord(i.DogIncident);
return RedirectToAction("Index");
}
}

How do I return a 404 status where invalid parameters are passed to my ASP.NET MVC controller?

I want to return a HTTP status 404 if invalid arguments are passed to my controller. For example if I have a controller that looks like:
public ActionResult GetAccount(int id)
{
...
}
Then I want to return a 404 if say urls such as these are encountered:
/GetAccount
/GetAccount/notanumber
i.e. I want to trap the ArgumentException that is thrown.
I know I could use a nullable type:
public ActionResult GetAccount(int? id)
{
if(id == null) throw new HttpException(404, "Not found");
}
But that's pretty icky and repetitious.
I was hoping I could add this to my controllers where necessary:
[HandleError(View="Error404", ExceptionType = typeof(ArgumentException))]
public class AccountsController : Controller
{
public ActionResult GetAccount(int id)
{
...
}
}
But that doesn't appear to work well.
I saw this post and this answer which nearly solves my problem:
In that answer an abstract BaseController is created from which you derive all your other controllers from:
public abstract class MyController : Controller
{
#region Http404 handling
protected override void HandleUnknownAction(string actionName)
{
// If controller is ErrorController dont 'nest' exceptions
if (this.GetType() != typeof(ErrorController))
this.InvokeHttp404(HttpContext);
}
public ActionResult InvokeHttp404(HttpContextBase httpContext)
{
IController errorController = ObjectFactory.GetInstance<ErrorController>();
var errorRoute = new RouteData();
errorRoute.Values.Add("controller", "Error");
errorRoute.Values.Add("action", "Http404");
errorRoute.Values.Add("url", httpContext.Request.Url.OriginalString);
errorController.Execute(new RequestContext(
httpContext, errorRoute));
return new EmptyResult();
}
#endregion
}
This works great at handling unknown actions with a 404 but doesn't allow me to handle invalid data as a 404.
Can I safely override Controller.OnException(ExceptionContext filterContext) like this:
protected override void OnException(ExceptionContext filterContext)
{
if(filterContext.Exception.GetType() == typeof(ArgumentException))
{
filterContext.ExceptionHandled = true;
this.InvokeHttp404(filterContext.HttpContext);
}
else
{
base.OnException(filterContext);
}
}
On the surface it seems to work, but am I storing up any problems by doing this?
Is this semantically correct thing to do?
Best way? Action method selector attribute!
To actually avoid nullable method arguments I suggest that you write an Action Method Selector attribute that will actually only match your action method when id is supplied. It won't say that argument wasn't supplied but that it couldn't match any action methods for the given request.
I would call this action selector RequireRouteValuesAttribute and would work this way:
[RequireRouteValues("id")]
public ActionResult GetAccount(int id)
{
...
}
Why is this the best solution for your problem?
If you look at your code you'd like to return a 404 on actions that match name but parameter binding failed (either because it wasn't supplied or any other reason). Your action so to speak requires particular action parameter otherwise a 404 is returned.
So when adding action selector attribute adds the requirement on the action so it has to match name (this is given by MVC) and also require particular action parameters. Whenever id is not supplied this action is not matched. If there's another action that does match is not the issue here because that particular action will get executed. The main thing is accomplished. Action doesn't match for invalid route request and a 404 is returned instead.
There's an app code for that!
Check my blog post that implements this kind of attribute that you can use out of the box. It does exactly what you're after: it won't match your action method if route data provided doesn't have all required values.
Disclaimer: this does not cover all the cases
For urls in your examples, returning 404 can be done in single line. Just add route constraint for id parameter.
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index" }, // Parameter defaults
new { id = #"\d+" } // restrict id to be required and numeric
);
And that's all. Now any matching url that has no id or id is not numeric, autimatically triggers not found error (for which there are plenty of ways to handle, one in your example, another by using custom HandleErrorAttribute, etc). And you can use non-nullable int parameters on your actions.
I managed to get this working by adding this route at the end of all routes:
routes.MapRoute("CatchAllErrors", "{*url}",
new { controller = "Error", action = "NotFound" }
);
Note: First I followed this: How can I properly handle 404 in ASP.NET MVC?

Resources