I'm writing a simple forum with ASP, using the MVC template with user login.
It's really new to me and kind of difficult to grasp. I managed to implement posting topics and comments, deleting them but the edit is giving me some major problem.
Here's my action:
public ActionResult EditReply()
{
int.TryParse((string)Url.RequestContext.RouteData.Values["id"], out int id);
Topic topic = db.Topics.Find(id);
int.TryParse((string)Url.RequestContext.RouteData.Values["id"], out int commentId);
Comment comment = topic.Comments.Find(x => x.Id == commentId);
return View(comment);
}
I get "object reference not set to an instance of an object". Debugging shows me that the comment variable is empty due to unsuccessful getting of the ID because it seems that the commentId variable's value is 0. It works for topicId but won't work for the commentId. However, the URL where I get the error shows http://localhost:49834/Home/EditReply?TopicId=16&CommentId=43
so the Id is there but I cannot extract it. I'm really new to ASP. Any sort of input will be greatly appreciated.
Thanks!
If you're using ASP.NET MVC then a simple update to your Action's signature will get the values you need.
public ActionResult EditReply(int TopicId, int CommentId)
{
Topic topic = db.Topics.Find(TopicId);
Comment comment = topic.Comments.Find(x => x.Id == CommentId);
return View(comment);
}
This will use the default model binding that is built in to MVC.
MVC will do the work for you. Define your action as
public ActionResult EditReplay(int topicId, int commentId) { …
And the model binding will use the query string or form values to populate parameters. With MVC you rarely, if ever, need to directly look at the request.
(If the parameters are optional, make the types nullable: int? commentId.)
Related
I am trying to implement POST-Redirect-GET in MVC using multiple named parameters.
I can do this with one parameter:
return RedirectToAction("MyGetView", new { bookId = id });
I could hardcode multiple parameters:
return RedirectToAction("MyGetView, new {bookId = id1, bookId=id2});
But how do I get from an IEnumerable< int> of Ids, which is of variable length, to a correct query string without constructing it by hand?
My current code looks like this:
var querystring= string.Join("", BookIds.Select(x => string.Format("bookId={0}&", x)));
querystring= querystring.Trim('&');
return Redirect("MyGetView?" + querystring);
It works fine, but it seems like there should be a better way.
(I want the parameters to be visible in the URL, so that users can bookmark the page. I believe this means I cannot use TempData.)
First, it's not wrong to use query parameters where the ASP.NET MVC Routes fail to work, and they do with arrays (basically). How is your "clean" URL route supposed to look like?
Example: /Books/View/6 (Works nice for one Book, 6 is the int ID)
But what do you want to have for multiple Books? /Books/View/6,5,134 maybe?
In this case you could just use your own convention of formatting the Url as a list of IDs.
Your redirect would look like: return RedirectToAction("View", "Book", new { Id = allIds.Join(",") });
And your View action can support this:
public ActionResult View(string id)
{
if (id == null)
throw new Exception... // Not expected
var ids = id.Split(new char[]{ ',' });
// Further processing...
}
If your're not satisfied with this approach (you might have issues when number of items is getting bigger) you can see what others tried, but it's basically what you already do, or I'm not sure if the other solutions are worth the effort.
Currently I am working with simple Forum module in ASP.NET MVC 3 which i will add later to my main application. The possibility to link to certain blocks like div used to present thread replies is very useful.
I figured out something which work and propably will be enough for my needs, I just wonder if there is something more elegant and simple aswell. I found a solution using ActionFilters, and since I'm quite begginner in MVC I would like to find some easier solution (if exists). Well i will propably learn ActionFilters soon aswell :)
So here is what I've done:
public ActionResult ShowThread(int id, int? postID)
{
var thread = db.ForumThreads.Find(id);
if (postID != null)
{
return Redirect(Url.Action("ShowThread",new {id=id})+"#post-"+postID.ToString());
}
return View(thread);
}
I know it is quite simple, but it is working. Also it doesn't check if the postID is valid yet, but it is not a part of question.
The solution is to use one of RedirectToAction overloads: http://msdn.microsoft.com/en-us/library/dd470154(v=vs.108).aspx.
Perhaps, in your case, the next one would do: http://msdn.microsoft.com/en-us/library/dd460291(v=vs.108).aspx
And the call would be:
return this.RedirectToAction("ShowThread", new { id = id, post = postID });
Another (simpler, but not safer!) solution is to format the URL you need to redirect to and to use the Redirect() method like this:
var redirectUrl = string.Format("/Home/ShowThread/{0}?post={1}", id, postID);
return this.Redirect(redirectUrl);
Pay attention to your URL mappings, though. The examples above assume the method
public ActionResult ShowThread(int id, int? post)
is in HomeController and the default route has not been changed. Otherwise you should
adjust the URL prepending the controller name (w/o Controller), or
change your default route to map to the controller's name.
Just building a quick, simple site with MVC 3 RC2 and RavenDB to test some things out.
I've been able to make a bunch of projects, but I'm curious as to how Html.ActionLink() handles a raven DB ID.
My example: I have a Document called "reasons" (a reason for something, just text mostly), which has reason text and a list of links. I can add, remove, and do everything else fine via my repository.
Below is the part of my razor view that lists each reason in a bulleted list, with an Edit link as the first text:
#foreach(var Reason in ViewBag.ReasonsList)
{
<li>#Html.ActionLink("Edit", "Reasons", "Edit", new { id = Reason.Id }, null) #Reason.ReasonText</li>
<ul>
#foreach (var reasonlink in Reason.ReasonLinks)
{
<li>#reasonlink.URL</li>
}
</ul>
}
The Problem
This works fine, except for the edit link. While the values and code here appear to work directly (i.e the link is firing directly), RavenDB saves my document's ID as "reasons/1".
So, when the URL happens and it passes the ID, the resulting route is "http://localhost:4976/Reasons/Edit/reasons/2". So, the ID is appended correctly, but MVC is interpreting it as its own route.
Any suggestions on how I might be able to get around this? Do I need to create a special route to handle it or is there something else I can do?
I just downloaded the latest version of RavenDB and tried this out.
public class Entity {
public int Id { get;set; }
public string Text { get;set; }
}
When I saved it to RavenDB, the id in Raven was "entities/1", but the mapping in RavenDB client was able to successfully interpret the Id from what was in the database to the integer that I wanted.
var entity = session.Load<Entity>(1);
Assert.IsTrue(entity.Id == 1);
You do not need a extension method, and this way you would not need to alter any routes as mentioned above, because you will be dealing with good ol' integers. The string Ids were almost a deal breaker, but amazingly this works so RavenDB is back in the hunt.
Note: I figured this out from watching a Rob Ashton talk and realizing that all his document classes had integers as Ids.
I was having a similar issue and came up with my own solution before I found the link to Shiju Varghese blog post referenced by #jfar.
It might not be as clean and simple as the solutions provided in the blog post, but I do believe it can compete as a solution none the less. So here goes:
In a standard model class, when using RavenDB, we normally have an id property like so:
public string Id { get; set; }
What I did, was to add another id property like so:
public int? IdInt
{
get { return int.Parse(Id.Substring(Id.IndexOf("/") + 1)); }
}
This will give us the number part of the given RavenDB id.
I then had a class that looked something like this:
[Bind(Exclude = "IdInt")]
public class Item
{
public string Id { get; set; }
public int? IdInt
{
get { return int.Parse(Id.Substring(Id.IndexOf("/") + 1)); }
}
...
}
Note that I have excluded the IdInt property, as I don't want the model binder to handle it.
Furthermore, note, that the IdInt property is nullable. This is to avoid problems with the model binder later on, when we are creating new items and the Id property is null.
Then in the routes I declared a rule similar to this:
routes.MapRoute(
"WithParam", // Route name
"{controller}/{action}/{id}" // URL with parameters
);
Later on using an ActionLink or something similar we can do the following:
#Html.ActionLink("Details", "Details", new { id = item.IdInt })
And lastly when we press the rendered link, we are sent to the appropriate action which could look something like this:
public ActionResult Details(int id)
{
var item = _session.Load<Item>(id);
return View(item);
}
The reason why this will work is because of the Load method, which takes a ValueType as parameter and then automatically resolves the number to the correct RavenDB id.
What we have achieved with this approach is a url that looks something like this:
/items/details/1
As I said in the beginning, this solution is less elegant than the previous suggested solutions, however, this approach will give you a cleaner url as you can work with the id's as you normally would.
FYI, the link posted by #jfar was exactly the article. The following is the solution I used from the article:
Solution 2 - Modify ASP.NET MVC Route
Modify the ASP.NET MVC routes in the
Global.asax.cs file, as shown in the
following code:
routes.MapRoute(
"WithParam", // Route name
"{controller}/{action}/{*id}" // URL with parameters
);
We just put "*" in front of the id
variable that will be working with the
default Id separator of RavenDB
I would like to strip out non-numeric elements from the POST data before using UpdateModel to update the copy in the database. Is there a way to do this?
// TODO: it appears I don't even use the parameter given at all, and all the magic
// happens via UpdateModel and the "controller's current value provider"?
[HttpPost]
public ActionResult Index([Bind(Include="X1, X2")] Team model) // TODO: stupid magic strings
{
if (this.ModelState.IsValid)
{
TeamContainer context = new TeamContainer();
Team thisTeam = context.Teams.Single(t => t.TeamId == this.CurrentTeamId);
// TODO HERE: apply StripWhitespace() to the data before using UpdateModel.
// The data is currently somewhere in the "current value provider"?
this.UpdateModel(thisTeam);
context.SaveChanges();
this.RedirectToAction(c => c.Index());
}
else
{
this.ModelState.AddModelError("", "Please enter two valid Xs.");
}
// If we got this far, something failed; redisplay the form.
return this.View(model);
}
Sorry for the terseness, up all night working on this; hopefully my question is clear enough? Also sorry since this is kind of a newbie question that I might be able to get with a few hours of documentation-trawling, but I'm time-pressured... bleh.
Instead of using the automatic model-binding in your action method's parameters, you could accept the posted FormCollection and work with that. You might be able to (1) modify the values in this special collection, then (2) bind your model manually, using UpdateModel/TryUpdateModel.
for example,
public ActionResult Index(FormCollection formCollection)
{
DoWhateverToFormCollection(formCollection);
Team model;
// TO-DO: Use TryUpdateModel here and handle more nicely
// Should also pass in binding whitelist/blacklist to the following, if didn't remove from the formCollection already...
UpdateModel<Team>(model, formCollection);
// rest of your code...
}
Hopefully this should work as advertised, and best of luck!
I believe you can use a Custom Model Binder for this. Scott Hanselman has an article here that describes the process, using the concept of splitting a DateTime into two separate parts as an example.
What if a user hits my site with http://www.mysite.com/Quote/Edit rather than http://www.mysite.com/Quote/Edit/1000 In other words, they do not specify a value for {id}. If they do not, I want to display a nice "Not Found" page, since they did not give an ID. I currentl handle this by accepting a nullable int as the parameter in the Controller Action and it works fine. However, I'm curious if there a more standard MVC framework way of handling this, rather than the code I presently use (see below). Is a smoother way to handle this, or is this pretty mush the right way to do it?
[HttpGet]
public ActionResult Edit(int? id)
{
if (id == null)
return View("QuoteNotFound");
int quoteId = (int)id;
var viewModel = new QuoteViewModel(this.UserId);
viewModel.LoadQuote(quoteId);
if (viewModel.QuoteNo > 0)
{
return View("Create", viewModel.Quote.Entity);
}
else
return View("QuoteNotFound");
}
Your other options would be
Having two Edit actions ; One with int id as its parameters and another without any parameters.
Only having Edit(int id) as your action and letting your controller's HandleUnknownAction method to do what it's supposed to do when your entity is not found (this is a bit more complicated).
But I like your approach the best, as it's simple and correctly handles the situation.
BTW, you don't need that local variable, you can just do this for better readability :
//...
if (!id.HasValue)
return View("QuoteNotFound");
var viewModel = new QuoteViewModel(this.UserId);
viewModel.LoadQuote(id.Value);
//...
No real issue with the way you have it but semantically it's not really an invalid quote number, its that they have navigated to an invalid route that they should not have gone to.
In this case, I would tend to redirect to /quote and if you really want to show a message to the user just show an error banner or similar (assuming you have that functionality in your master page etc).
public ActionResult Edit(int? id)
{
if (id == null)
{
// You will need some framework in you master page to check for this message.
TempData["error"] = "Error Message to display";
return RedirectToAction("Index");
}
...
}
Use route constraint
You can always define a route constraint in your routes.MapRoute() call that won't pass through any requests with undefined (or non-numeric) id:
new { id = "\d+" }
This is a regular expression that checks the value of id to be numeric.
You will probably have to create a new route that defines this, because for other controller actions you probably don't want routes to be undefined. In this case, your controller action wouldn't need a nullable parameter, because id will always be defined.
Don't be afraid of using multiple routes. With real life applications this is quite common.