ASP.NET MVC2 Parent Controller Not Redirecting - c#

I have an ASP.NET MVC2 application that uses a parent controller to setup specific variables that are used around the app. I also implement validation to make sure that an ID in the URI exists in the database. If it does not, I redirect and stop the execution of the script.
My parent controller looks something like this:
// Inside class declaration
// Set instance of account object to blank account
protected Account account = new Account();
protected override void Initialize(System.Web.Routing.RequestContext requestContext) {
// Call parent init method
base.init(requestContext);
// Check to make sure account id exists
if (accountRepos.DoesExistById(requestContext.RouteData.Values["aid"].ToString()) {
account = accountRepos.GetById(requestContext.RouteData.Values["aid"].ToString());
} else {
requestContext.HttpContext.Response.Redirect("url");
requestContext.HttpContext.Response.End();
}
}
At first this worked, but now when an incorrect id is entered, it doesn't redirect and throws a NullPointerException when the Account class is used. I originally just declared the account variable rather instantiating it, but that also proved to throw exceptions and didn't redirect.
The reason I try to end the execution of the script is because I want to make sure that it stops even if the redirect doesn't work. Kinda like calling exit() after header() in PHP :p . If I am doing this wrong, I would appreciate any pointers.
I'm just wondering how I can fix this.
Any help is greatly appreciated =D

I don't think that's the proper way to do what you want. Instead you should use route constraints on your routes to make sure the id exists, and fall back from there in a "catch all" route.
Something like this:
Routes.MapRoute("Name", "Url", new { ... }, new {
Id = new IdConstraint() // <- the constraint returns true/false which tells the route if it should proceed to the action
});
The constraint would be something like this:
public class IdConstraint : IRouteConstraint {
public bool Match(
HttpContextBase Context,
Route Route,
string Parameter,
RouteValueDictionary Dictionary,
RouteDirection Direction) {
try {
int Param = Convert.ToInt32(Dictionary[Parameter]);
using (DataContext dc = new DataContext() {
ObjectTrackingEnabled = false
}) {
return (dc.Table.Any(
t =>
(t.Id == Param)));
};
} catch (Exception) {
return (false);
};
}
}
This is what I use with my routes to make sure that I'm getting an Id that really exists. If it doesn't exist, the constraint returns a false, and the route does not execute and the request continues down the route chain. At the very bottom of your routes you should have a generic catch all route that sends your user to a page that tells them what they want doesn't exist and to do X or X (something along those lines, I'm just coming up with scenarios).

Related

How to hide parameters from querystring (url) ASP.NET

I'm trying to hide the parameters from the querystrings in my web application.
I have been able to do that by using the session to store temporary variables. So it would work like this:
1. Click the view profile button:
href="#Url.Action("RedirectWithId", "Redirect", new { act = "ProfileView", ctrl = "User", id = member.Id})"
2. Calls the redirection method and stores the temp data:
public class RedirectController : Controller
{
public ActionResult RedirectWithId(string act, string ctrl, int id)
{
Session["temp_data"] = id;
return RedirectToAction(act, ctrl);
}
}
3. Use it in the action method without the parameter:
public ActionResult ProfileView()
{
if (Session["temp_data"] == null)
{
return Redirect(Request.UrlReferrer.ToString());
}
int id = (int)Session["temp_data"];
var model = GetUserById(id);
return View(model);
}
So it works just fine, however, this way to hide parameters doesn't handle the case where let's say I go to a first profile(id 4), and then go to a second one(id 8). If from the second profile I press the back button on the navigator trying to go back to the first profile(id 4), I'm going to be redirected to the current profile(id 8), since 8 is the current value of the Session["temp_data"].
Is there a way to handle this perticular case? Or is the another totally different and better way to hide parameters in the URL?
Thank you!
You can try this instead of Session
TempData["temp_data"]
I came to the conclusion that since I am already using authorizations and roles within my application, I don't need to always hide the parameters. I can simply hide whenever I am passing a complex object as a parameter.

Use an "If this already exists in the API" type statement

I have an API that I've created. I've (finally) managed to both GET and POST with it. Now I want to check the POST before it gets submitted.
I have a class with properties (is that the right word? I'm still learning the lingo) of id, name, city, state, and country.
I have a form with a button, and the button has a click event method that looks like this:
protected void submitButton_Click(object sender, EventArgs e)
{
void Add_Site(string n, string ci, string s, string co)
{
using (var client = new HttpClient())
{
site a = new site
{
name = n,
city = ci,
state = s,
country = co
};
var response = client.PostAsJsonAsync("api/site", a).Result;
if (response.IsSuccessStatusCode)
{
Console.Write("Success");
}
else
Console.Write("Error");
}
}
Add_Site(nameText.Text, cityText.Text, stateText.Text, countryText.Text);
}
Now, at this point, it's working as expected. However, I'd like to limit it.
What I want to do is to have it look at the nameText.Text value. Before it runs the POST statement, I want it to look at the other values in the GET of the API and make sure that name doesn't already exist.
While I know that I could probably update the database to make the name field unique, I'd rather do it programatically in C#.
Is this something that's possible within my C# code? If so, what function would I use and how would I get it to return the Site.Name attribute to compare my nameText.Text value?
EDIT:
Here is the code for the POST as requested in one of the comments. Note: This was auto-generated by Visual Studio when I added the controller.
// POST: api/site
[ResponseType(typeof(site))]
public IHttpActionResult Postsite(site site)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.site.Add(site);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = site.id }, site);
}
I wouldn't have any idea where to even start with adding an "If the name already exists, throw an error," so some kind of walkthrough is plenty.
Here's code for looking in the database if any sites have the provided name (site.Name):
if (db.site.Any(o => o.Name == site.Name))
return BadRequest("Name already exists.");
I eventually determined that I should modify the POST as said in the comments. The solution I used ended up in this question: How to limit POST in web API

ASP.NET CORE, Web API: No route matches the supplied values

PLEASE NOTE: This question was asked in 2016. The original answer to this problem was to update the microsoft api versiong package. In the current days, the problem reoccurs, but for other reasons.
Original Question:
i have some problems with the routing in asp.net core (web api).
I have this Controller (simplified):
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[Controller]")]
public class DocumentController : Controller
{
[HttpGet("{guid}", Name = "GetDocument")]
public IActionResult GetByGuid(string guid)
{
var doc = DocumentDataProvider.Find(guid);
if (doc == null)
return NotFound();
return new ObjectResult(doc) {StatusCode = 200};
}
[HttpPost]
public IActionResult Create([FromBody] Document doc)
{
//... Creating Doc
// Does not work
var val = CreatedAtRoute("GetDocument", new {guid = doc.Guid.ToString("N")}, document);
// or this:
CreatedAtRoute("GetDocument", new { controller = "Document", guid = doc.Guid.ToString("N")}, document);
// neither this
var val = CreatedAtRoute("GetDocument", new { version = "1", controller = "Document", guid = doc.Guid.ToString("N")}, document);
return val;
}
}
If i call Create, the document is created and the routing object was created but i get the error "No route matches the supplied values" and get a 500 status.
I can call the GetByGuid directly, without any problems.
I couldn't find any debugging help for asp.net core (like any existing routing debugger).
I would appreciate any help!
EDIT
Looks like it would be a bug from microsoft's versioning package.. if i define the fix route /api/v1/[Controller] it's working.
But that's not a solution for me.
ASP.net core 3
Why this problem occurs:
As part of addressing dotnet/aspnetcore#4849, ASP.NET Core MVC trims the suffix Async from action names by default. Starting with ASP.NET Core 3.0, this change affects both routing and link generation.
See more: ASP.NET Core 3.0-3.1 | Breaking Changes | Async suffix trimmed from controller action names
As #Chris Martinez says in this Github Issue:
.Net Core 3.0 and using CreatedAtRoute result in No route matches the supplied values. #558:
The reason for the change was not arbitrary; it addresses a different bug. If you're not affected by said bug and want to continue using the Async suffix as you had been doing.
How to solve
Re-enable it:
services.AddMvc(options =>
{
options.SuppressAsyncSuffixInActionNames = false;
});
You should now pass the createActionName parameter including the Async suffix like this:
return CreatedAtAction("PostAsync", dto)
I know this post is from 2017 but still i just faced the same problem and ended up here. And as it looks like you never found your mistake I'll write it here for anyone else that founds this post.
The problem is that when you call:
CreatedAtRoute("GetDocument", new { version = "1", controller = "Document", guid = doc.Guid.ToString("N")}, document);
You are telling the program to look for a "GetDocument" function that receives 3 parameters, in this case 3 strings but your actual "GetDocument" definition receives only 1 string that is your "guid":
[HttpGet("{guid}", Name = "GetDocument")]
public IActionResult GetByGuid(string guid)
{
var doc = DocumentDataProvider.Find(guid);
if (doc == null)
return NotFound();
return new ObjectResult(doc) {StatusCode = 200};
}
So for it to work you should have it like this:
CreatedAtRoute("GetDocument", new { guid = doc.Guid.ToString("N")}, document);
Another option would be to create a new get method with 3 strings and maybe you'll have to call it something different than "GetDocument".
Hope this helps the next one that comes looking for this :D
In my case it was a copy/paste error. The CreatedAtAction method had the wrong action name parameter. Make sure the first parameter (the actionName parameter) matches the action name (the function name). The corrected code is seen below:
I'll answer my own question:
It was really a bug in the versioning package of microsoft and it's gonna be fixed soon.
ASP.Net Core: internal routing fails with CreatedAtRoute #18
I just used return CreatedAtAction("ActionMethodName", dto); instead until they fix that bug.
Just in case, if you are like me, don't like to inherit from any of the in built ControllerX base classes, then you might face this issue, if you use following statement
new CreatedAtActionResult(nameof(GetBookOf),
nameof(BooksController),
new {id = book.Id.ToString("N")},
book)
It's because second parameter in the above statement, expect us to pass the constructor name without Controller suffix.
Hence, if you change like following, then it would work just fine.
new CreatedAtActionResult(nameof(GetBookOf),
"Books",
new {id = book.Id.ToString("N")},
book)
Another reason this error occurs is b/c when you use the CreatedAtAction to return 201, ASP.NET will add a Location header to the response with the url where the resource can be accessed. It uses the actionName to do this, so the method name needs to match a valid method on your controller. For example:
return CreatedAtAction("GetUser", new { id = user.UserId }, user);
If GetUser does not exist, the call will fail. Better practice probably to use:
return CreatedAtAction(nameof(GetUser), new { id = user.UserId }, user);
dev guide
apidocs
This worked for me...
https://www.josephguadagno.net/2020/07/01/no-route-matches-the-supplied-values
my get-async was...
[HttpGet("{id}", Name = nameof(GetSCxItemAsync))]
public async Task<ActionResult<SCxItem>> GetSCxItemAsync(Guid id)
but in order to get around route error I had to add the following before the function declaration...
[ActionName(nameof(GetSCxItemAsync))]
I had the same symptoms but the cause was something else. Basically it doesn't like the fact that my action method ends with Async. It works when I either rename the action method from GetAsync to Get, or add [ActionName("GetAsync")] to the action.
Source: https://github.com/microsoft/aspnet-api-versioning/issues/601
I'm going to stick this in here for some other poor unfortunate soul as I've spent far too long working out what I was doing wrong.
This is wrong:
app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
What this does is mask the problem by returning the default route which is [controller], when what you want it to return is [controller]\{newly generated id}.
This is correct:
return CreatedAtAction(nameof(GetUser), new { id = user.UserId }, user);
However there is an edge case where this fails, this being when user.UserId is null. I think that this fails because the id parameter is required, so cannot be null, thus the route doesn't bind. It's good that it fails because if it didn't then you would be creating a junk route, but the error message is more cryptic than perhaps it could be.
In my case I was returning the Id on the input object, which was null, not the newly generated Id on the output object.
Instead of this:
CreatedAtRoute("GetDocument", new { version = "1", controller = "Document", guid = doc.Guid.ToString("N")}, document);
Change your return to the following:
return CreatedAtRoute("GetDocument",doc, null);
If your route has parameters (for example GetbookById(int id)) then you need 3 parts to the CreatedAtRoute for example:
book b = new book(); // The Created new book.
var routeValues = new { id = book.Id}; // Represents the parameter(s) to GetbookById
return CreatedAtRoute( nameof(GetbookById), routeValues, b);
More in: Created, CreatedAtAction, CreatedAtRoute Methods In ASP.NET Core Explained With Examples
you must give a name for a method like below and return
[HttpGet("{id}",Name = "MethodName")]
public async Task<IActionResult> MethodName(int id)
{
var baseItem = await _baseItemService.BaseItemByIdAsync(baseItemId);
return Ok(baseItem);
}
then you need return CreatedAtRoute in Post or Put Method to get Item by Id or etc such as
var (errorMessage, baseItem) = await _baseItemService.AddBaseItem(model);
if (baseItem != null) return CreatedAtRoute("MethodName", new { baseItemId = baseItem.BaseItemId }, baseItem);
return BadRequest(errorMessage );
me must give the same method name
I had similar issues though not with get-async. It turned out that I was using a wrong action name, the easiest work around is to ensue your using the correct action name and I advice sometimes to just copy and past it.

MVC Response.Redirect not working

I have a method inside MVC Controller which is called from href inside anchor tag.
public ActionResult DoSomething(string value)
{
if(true)
{
return new RedirectResult("http://google.com");
}
}
when I debug and hit that method Response.Redirect does nothing no exceptions either. any ideas?
Thanks in advance!
Use Redirect
return Redirect("http://www.google.com");
Response.Redirect is not preferred way of doing redirects in asp.net mvc
Response.Redirect and ASP.NET MVC – Do Not Mix
Update: It seems that you are trying to redirect ajax request. If you redirect ajax request, your main page won't be redirected.
There are a few things you need to do here to avoid all these issues.
Starting with the AJAX errors you're getting, they most like relate to the javascript debugger, which Microsoft refer to as "BrowserLink".
If you use Firefox or Chrome, this feature simply doesn't work, which is probably the easiest way to avoid the issue, however you can disable the feature here:
You can change the default browser to run the website in just to the left.
In terms of Response.Redirect, I think that's been well covered, you should use return Redirect() instead, however your code needs to be refactored to allow for that.
Assuming that method is a helper method which is required to be separate from the controller itself, there are a couple of main approaches to doing what you're trying to to do.
1) Magic Values
This could include "redirect1" or also commonly null, and would look something like:
public ActionResult MyAction
{
string data = ProcessString("some data");
if (data == null) { return Redirect("google.com"); }
}
public string ProcessString(string input)
{
if (condition) { return null; }
string output = input + "!"; // or whatever you need to do!
return input;
}
2) Handle via exceptions
Assuming the problem is that the data is in some way bad, and you want to redirect away because you cant process it, Exception handling is most likely the way to go. It also allows for different types of exceptions to be raised by a single method and handled independently without having magic values which then can't be used as normal data.
public ActionResult MyAction
{
string data; // remember this will be null, not "" by default
try
{
data = ProcessString("some data");
}
catch (OwlMisalignedException ex)
{
return RedirectToAction("Index", "Error", new { exData = ex.Code });
}
// proceed with controller as normal
}
public string ProcessString(string input)
{
if (condition)
{
throw new OwlMisalignedException(1234);
// this is (obviously) a made up exception with a Code property
// as an example of passing the error code back up to an error
// handling page, for example.
}
string output = input + "!"; // or whatever you need to do!
return input;
}
By using that approach you can effectively add extra return states to methods without having to fiddle with your return type or create loads of magic values.
Don't use throw Exception - either use one of the more specific types ArgumentException and ArgumentNullException will probably come in handy, or create your own types if needs be.
You'll find info on creating your own Exception types on here easily enough.

How are args represented (if at all) in Web API Attribute Routing annotations and if they aren't, how are they discovered?

I am currently using attribute routings such as this:
[Route("api/InventoryItems/{ID}/{packSize:int}/{CountToFetch:int}")]
...and am using even longer ones (with more "pieces," or arguments), and in practice they work fine.
However, I need to refactor this (args masquerading as part of the path considered bad practice) so that the URI passed by the client is not like so:
//http://<machineName>:<portNum>/api/InventoryItems/<IDVal>/<packSizeVal>/<CountToFetchVal>
http://platypus:8675309/api/InventoryItems/42/24/50
...but rather like this:
//http://<machineName>:<portNum>/api/InventoryItems/?<argName>=<argVal>?<argName>=<argVal>?<argName>=<argVal>
http://platypus:8675309/api/InventoryItems?ID=42?packSize=24?CountToFetch=50
Currently I can grab the args passed within the "path" info and pass them from the Controller (where they arrive) to the Repository (which uses them to get the precise data required).
For example, this Controller method:
[System.Web.Http.Route("api/Departments/{ID:int}/{CountToFetch:int}/{dbContext=03}")]
public IEnumerable<Department> GetBatchOfDepartmentsByStartingID(int ID, int CountToFetch, string dbContext)
{
return _deptsRepository.Get(ID, CountToFetch, dbContext);
}
...has the values passed via a URI from the client assigned to the method parameters. What, if anything, do I need to change in this code for args passed in the URI via the "?=" method to also be assigned to the method parameters?
Can I do this, with those args simply stripped out of the Attribute Routing annotation, like so:
[System.Web.Http.Route("api/Departments")]
public IEnumerable<Department> GetBatchOfDepartmentsByStartingID(int ID, int CountToFetch, string dbContext)
{
return _deptsRepository.Get(ID, CountToFetch, dbContext);
}
?
...or possibly leave it as-is, with just the format of the URI changing (but nothing in the Controller)?
UPDATE
I wasn't expecting it to work, but I can definitely verify that leaving the server code as is, and replacing the URI with this jazz:
"?<argName>=<argVal>"
...does not work - it returns null without even hitting my Controller method!
UPDATE 2
With an URI like this:
http://localhost:28642/api/InventoryItems/PostInventoryItem?id=42?pack_size=12?description=ValuableDesc?vendor_id=venderado?department=42?subdepartment=85?unit_cost=2.50?unit_list=3.75?open_qty25.25?UPC_code=12345?UPC_pack_size=24?vendor_item=someVendorItem?crv_id=9898987?dbContext=03
...I can reach the Controller if I remove all args from the routing attribute and the method signature:
[Route("api/InventoryItems/PostInventoryItem")]
public void PostInventoryItem()
{
HandheldServerGlobals.SaveTypes st = HandheldServerGlobals.SaveTypes.CSV; //Works (C:\Program Files (x86)\IIS Express\SiteQuery3.csv created) // <-- I reach the breakpoint on this line, but...:
//commented out for now:
//_inventoryItemRepository.PostInventoryItem(id, pack_size, description, vendor_id, department, subdepartment, unit_cost, unit_list, open_qty, UPC_code, UPC_pack_size, vendor_item, crv_id, dbContext, st);
}
...but where/how do I get the args passed in the URI now?
By annotating the Controller method with "[FromURI]":
[Route("api/InventoryItems/PostInventoryItem")]
public HttpResponseMessage PostInventoryItem([FromUri] InventoryItem ii)
{
_inventoryItemRepository.PostInventoryItem(ii.ID, ii.pksize, ii.Description, ii.vendor_id, ii.dept,
ii.subdept, ii.UnitCost, ii.UnitList, ii.OpenQty, ii.UPC, ii.upc_pack_size, ii.vendor_item, ii.crv_id);
var response = Request.CreateResponse<InventoryItem>(HttpStatusCode.Created, ii);
string uri = Url.Link("DefaultApi", new { id = ii.ID });
response.Headers.Location = new Uri(uri);
return response;
}
...and by replacing all but the first "?" in the URI passed in with "&"

Categories