How does UrlHelper.GenerateContentUrl works? - c#

I dont get it, I have this code:
return JavaScript(string.Format(
"window.location = '{0}'",
UrlHelper.GenerateContentUrl("Index", this.HttpContext)));
The code is inside two pretty generic "Create" methods that works with POST. Each of the two methods are in different controller classes.
Now for method A that is called with the URL http://localhost:56688/Businessrule/Create, when the code is executed I get redirected to http://localhost:56688/Index.
But for method B called from http://localhost:56688/FormulaField/Create I get redirected to http://localhost:56688/FormulaField/Index.
... really I don't get it, and the microsoft documentation isn't helping out much http://msdn.microsoft.com/en-us/library/system.web.mvc.urlhelper.generatecontenturl.aspx (now, IMHO, that's a pretty crappy documentation for a method)

Sounds like your missing the controller name. apperently, you're being redirected to the Index action in the same controller.
It's what the MVC Route engine do, if he does not find the controller name, he assign a default value, in this case, the controller from witch the action has been executed.
Try something like :
UrlHelper.GenerateContentUrl(#"~\ControllerName\Index", this.HttpContext)

So, as pointed by asawyer how it works is answered by the code itself:
https://github.com/aspnet/AspNetWebStack/blob/master/src/System.Web.Mvc/UrlHelper.cs
It turns out that if the string that you pass begin with "~" the method will call PathHelpers.GenerateClientUrl, but if the string doesn't begin with "~" it will just return the same string unchanged.
But still I don't undertand why I'm getting different results. Anyway seems that I'll have to look closer to the raw response passed to the browser...

Related

Url.Action return the current link in browser

I am currently on the page /Customer/Edit/13244.
When I use #Url.Action("Edit", "Customer") on the same page it returns me /Customer/Edit/13244, but I want it to return /Customer/Edit/.
Kindly tell me how to fix this issue.
This is a "feature" of MVC that many people find unnatural and was previously reported as a bug.
Microsoft's official response:
Ultimately if you want the most control over what gets generated for a URL there are a few options to consider:
Use named routes to ensure that only the route you want will get used to generate the URL (this is often a good practice, though it won't help in this particular scenario)
Specify all route parameters explicitly - even the values that you want to be empty. That is one way to solve this particular problem.
Instead of using Routing to generate the URLs, you can use Razor's ~/ syntax or call Url.Content("~/someurl") to ensure that no extra (or unexpected) processing will happen to the URL you're trying to generate.
Actually, this bug only rears its ugly head when you try to re-purpose an action method name. If you use a different action method name other than Edit in the case where it is not followed by id, this problem will magically disappear.
You will need to use (assuming your using the default route with id = UrlParameter.Optional
#Url.Action("Edit", "Customer", new { id = "" })

How MVC Web Application gets parameters without Request.Querystring?

I'm working on a MVC web application. I need to download a file which I've stored as a byte[] stream in DB and its working fine. What I used to do on a button click I call a JS function and that calls a function in the C# backend and eventually download the file. Following is my JQuery code.
var DownloadDRR = function ()
{
var InvoiceId = $(".invoiceid").text();
location.href = location.origin + "/Invoicing/DownloadDRR?InvoiceId=" + InvoiceId;
}
And in the backend I normally get query string like this
Request.Querystring("InvoiceId");
But accidental I've discovered in my application if I write the following it still gets the InvoiceId without using Request.QueryString().
public FileResult DownloadDRR(int InvoiceId)
{
InvoicingService Invoices = new InvoicingService(Client);
byte[] RawExcel = Invoices.GetExcelService(InvoiceId);
MemoryStream stream = new MemoryStream(RawExcel);
stream.Seek(0, SeekOrigin.Begin);
return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "test.xlsx");
}
Can anyone explain why please?
MVC specifically automates a lot of that binding (model binding is the term used).
myurl.com/MyController/MyMethod?something=a&anotherthing=1234
//example 1
public ActionResult MyMethod(string something, string anotherthing)
//example2
public ActionResult MyMethod(string something, int anotherthing)
It works for both examples. Even though your querystring technically only contains string values, MVC will try to parse it to the desired type.
The only thing you need to pay attention to is that the querystring parameter names match the method's parameter names. The rest is done automagically :)
//example3
public ActionResult MyMethod(int something, int anotherthing)
In this example, something cannot be converted, as "a" cannot be put into an int. The method call will fail (expect an ASP.Net error page). However, there are ways around this:
If the type is nullable, the method will still be called, and null will be the value. int? something will be set as null if conversion fails, and this makes sure the method still gets called.
You can make it an optional parameter: MyMethod(int anotherthing, int something = 0). Notice the inversion of the parameters. Optional parameters must always be placed after normal (required) parameters! This will make sure that, when something either cannot be converted (or simply isn't part of the querystring), it will receive the default value you specified (in my example, 0)
Some remarks:
You can write custom modelbinders that go way deeper than just converting a value. However, this is not default MVC behavior. It's still good to know you can add it if you need it.
Not all parameters are always part of the querystring. If you make a POST request (as opposed to the more lax GET request), you won't see a querystring. The values are still passed, but not as part of the requested URL. This is a topic you can find tons of information on via Google.

Is RedirectToAction in C# expensive?

I've some code like the following;
We're going to create a Note but we may know the CustomerId when we do so I've two URLs;
public ActionResult CreateByCustomer(int id)
{
Session["ncAppointmentId"] = 0;
Session["ncNoteDate"] = null;
SetConsultant(0);
return RedirectToAction("Create", "Note", new { id = id });
}
public ActionResult Create(int id = 0)
{
int _CustomerId = id == 0 ? Convert.ToInt32(Session["CustomerId"]) : id;
Session["TemplateIds"] = null;
and so on.......
ViewBag.CustomerId = _CustomerId;
When I look at the performance in Firebug the RedirectToAction causes a GET with a '302 Found' status which can incur up to 1 second's delay.
If I change the RedirectToAction line to
return Create(0);
Then the GET doesn't happen and performance is improved.
But I'm looking for opinions on what the downside is or what I've missed ?
The RedirectToAction result tells the client to request a new page, so it will naturally incur overheads since the client is now having to make a second request to your server. Sometimes this is necessary, sometimes - as in your example - not.
Generally I use RedirectToAction to return the client to a specific page after performing an action such as creating a note. This can be useful if you're on a page that lists notes and you want to refresh the page after creating a new one. The end result is that the page is refreshed, and the Create action does not appear in the user's browser history.
If your Create method returns a View, there can be some interesting side effects of calling it directly. In general the MVC code will handle it, but you can get some weird results - like the client's URL being different to what you expect in the subsequent requests, etc. If you're OK with this, fine.
Another option would be to get rid of the CreateByCustomer action and simply call the Create view with a parameter - named customerID for instance. This gives you the same ability to call it different ways without having to have multiple entry points. The client's location would reflect (in the query string) the difference between Create and Create?customerId=12345 which may or may not be what you're after.
<opinion>
Some Style Notes:
If you're storing lots of session data, create a class to hold it instead of creating lots of entries in Session[].
It's not particularly difficult to use jQueryUI to create an in-page editor for your notes rather than defining a view - check out this example. More elegant too :P
</opinion>
The RedirectToAction method is going to return an HTTP response that has a Found status code and a Location URL pointing to the place you are redirecting the client. The cost is simply another GET request, which I would not consider expensive. The decision to redirect or not should be made based on whether it conceptually makes sense, not based on whether you are making one less GET request.
I don't entirely understand the motivation here, however. If you elaborate on why you are trying to redirect, maybe I can help you choose a pattern that makes more sense.
Typically, you would not name a method Create* in an HTTP API. The idiomatic, correct prefix is Post* or Put*, depending on whether you are adding a new resource (but naming it) or creating/replacing a resource (and naming it), respectively.
The big difference is regarding if you want the url to change to the "Create" one. If it's ok to show whatever you are showing with that url, then avoid the redirect. Redirect is useful when you have an old url and you want it to point to a new one and also in the situation when you want to avoid saving new stuff if the user refresh de page (as it will refresh only the redirect request and not the post).

Submit form with GET method

Alright, I lost the entire morning on this and I'm getting nowhere.
I have a good experience with MVC, been using it since the first betas back in 2008 but I can' t really figure this out.
I substantially have 2 GET methods: the first one renders a form. The second one is the method the form points to. I'm submitting using GET because it's a search form, and I want to have a bookmarkable URL with the parameters.
Something like this
[HttpGet]
public ActionResult DisplayForm()
{
Contract.Ensures(Contract.Result<ActionResult>() != null);
Contract.Ensures(Contract.Result<ActionResult>() is ViewResult);
return this.View();
}
[HttpGet]
public ActionResult Search(MyViewModel viewModel)
{
Contract.Requires<ArgumentNullException>(viewModel != null);
Contract.Ensures(Contract.Result<ActionResult>() != null);
Contract.Ensures(Contract.Result<ActionResult>() is ViewResult || Contract.Result<ActionResult>() is RedirectToRouteResult);
var result = this.validator.Validate(viewModel); //FluentValidation validation
if (!result.IsValid)
{
result.FillModelState(this.ModelState); //extension method that uses AddModelError underneath for ValidationMessageFor helpers on search form
return this.RedirectToAction(c => c.DisplayForm()); //MvcContrib redirect to action
}
ViewData.Model = viewModel;
return View();
}
The first time I submit the form, the viewData in the target method gets populated correctly.
If I go back and do another search, it's like the modelbinder "cached" the data I submitted the first time: the viewData always has the values from the first search. It resets only restarting the application.
I tried checking both ModelState and HttpContext.Request and they DO have the new data in them (not stale) but still the viewData gets populated with old data. I also tried overriding OnActionExecuting and OnActionExecuted simply to put a breakpoint in there and check the ModelState in those steps, and found nothing weird.
I also tried to call the Search method directly via the browser bar, since it's in GET and I can do it. Still, ModelState and Request contain the data I input, but the viewData has old data.
This is really getting to my nerves as I can't really understand what's going on.
Any help would really be appreciated.
Have you tried
ModelState.Clear()
When you're done with the search call?
I experimented a lot and I found out that the problem was on an actionfilter on a base class which I was unaware of. The "PassParametersDurignRedirect" filter of MvcContrib. Without it everything works fine.

How to get all active parameters in ASP.NET MVC (2)

I was wondering whether there is a way to create an ActionLink or similar, that changes only a few parameters of the actual query, and keeps all the other parameters intact. For example if I'm on an URL like http://example.com/Posts/Index?Page=5&OrderBy=Name&OrderDesc=True I want to change only the Page, or OrderBy parameter and keep all other parameters the same, even those I don't yet know of (like when I want to add a Search parameter or something similar too).
The header of my current action looks like this:
public ActionResult Index(int? Page, string OrderBy, bool? Desc)
and I'm only interested in the values that this controller "eats". I want however that when I extend this action (for example with a string Search parameter) the links should work the same way as before.
Here is what I did already:
Create a new RouteValueDictionary and fill it with everything from RouteData.Values
Problem: This only fills the parameters that are used in the Routing, so all other optional parameters (like Page) to the controller are lost
Add everything from HttpContext.Request.QueryString to the previous dictionary
This is what I am currently using
Problem: It might have some junk stuff, that the Controller didn`t ask for, and it doesn't work if the page was loaded using POST. You also don't have any ModelBindings (but this isn't much of a problem, because we are re-sending everything anyway)
Use HttpContext.Request.Params
Problem: this has too much junk data which imho one shouldn't add to a RouteValueDictionary that is passed to an ActionLink
So the questions:
Is there an RVD that has all the data that was passed to the Controller and was used by it?
Is this solution good, or are there any caveats I didn't think about (mainly in the context of changing a few query parameters while keeping the others intact)?
Is there a way to filter out the "junk" data from the Params object?
EDIT: Checked the RouteData.DataTokens variable, but it's usually empty, and doesn't contain everything I need. It seems to only contain parameters that are needed for the routing somewhere, but not all of the parameters.
Have a look in RouteData.DataTokens.
RouteData.DataTokens # MSDN documentation:
Gets a collection of custom values that are passed to the route handler but are not used when ASP.NET routing determines whether the route matches a request.
HTHs,
Charles

Categories

Resources