Why is the object I tried to get from QueryString empty? - asp.net

I am trying to create a blog site with asp.net core 3.1. I want to add a comment to the blog post, but I cannot get a query string to which blog post I posted.
I shared my codes as follows. Now, what is the reason why the id from the query string always returns 0?
Or what is the situation I am constantly doing wrong?
There is probably a very simple solution but it took me all day please can you help?
Entity;
public class Content : IEntity
{
public int Id { get; set; }
public int CategoryId { get; set; }
public int UserId { get; set; }
public virtual List<ContentComment> ContentComments { get; set; }
}
public class ContentComment : IEntity
{
public int id { get; set; }
public int UserId { get; set; }
public int ContentId { get; set; }
public string Comment { get; set; }
public DateTime CommnetCreateTime{ get; set; }
public virtual Content Contents { get; set; }
}
Controller :
public class BlogContentController : Controller
{
private IBlogService _blogService;
private IContentCommentsServices _contentCommentsServices;
public BlogContentController(IBlogService blogService, IContentCommentsServices contentCommentsServices)
{
_blogService = blogService;
_contentCommentsServices = contentCommentsServices;
}
public IActionResult Index(int id)
{
var blogModel = new ContentViewModel
{
Blogs = _blogService.GetById(id)
};
return View(blogModel);
}
public IActionResult CommentAdd()
{
var model = new ContentCommendViewModel()
{
ContentComments = new List<ContentComment>()
};
return Ok();
}
[HttpPost]
public IActionResult CommentAdd(ContentComment contentComment)
{
contentComment.ContentId = Convert.ToInt32(HttpContext.Request.Query["id"]);
_contentCommentsServices.Add(contentComment);
return RedirectToAction("CommentAdd");
}
}
View Page :
<div class="media-body mt-2" id="yorumyap">
<h5>Yorum Bırakın</h5>
<form asp-controller="BlogContent" asp-action="CommentAdd">
<div class="form-group">
<textarea name="Comment" class="form-control form-control-sm" rows="3" style="resize: none;" id="commenttext"></textarea>
</div>
<div class="text-right">
<button type="submit" class="btn btn-tekisimbilsim mb-2" id="commendAdd">Yorum Yap</button>
</div>
</form>
</div>

You are trying to get the 'id' from the query string of the request. This would require your post URL to look something like: https://example.com/BlogContent/CommentAdd?id=42. Based on the HTML you have provided, I bet your URL looks like: https://example.com/BlogContent/CommentAdd (you can determine what your URL looks like by using your browsers inspection tools to look at the form tag in the HTML). In order to get the desired behavior you will need to update your form tag to look something like this:
<form asp-controller="BlogContent" asp-action="CommentAdd" asp-route-id="#Model.BlogId">
the important part in this is the addition of the 'asp-route-id' tag helper. You can find information about this tag helper (listed as 'asp-route-{value}') and others at:
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-3.1#the-form-tag-helper
But wait, there's more! In this example I just gave more than likely your URL will turn out to be https://example.com/BlogContent/CommentAdd/42 (I say more than likely because this is determined by your route configuration). This will not give you the expected result and the id will again be zero! So you have a couple of options. The easiest is to change the 'asp-route-{value}' tag helper to be something like 'asp-route-contentId'. This would give you html that looks like:
<form asp-controller="BlogContent" asp-action="CommentAdd" asp-route-contentId="#Model.BlogId">
which would generate a URL that would be https://example.com/BlogContent/CommentAdd?contentId=42. You would then need to update your controller code to retrieve the id to look like this:
contentComment.ContentId = Convert.ToInt32(HttpContext.Request.Query["contentId"]);
There are a myriad of other options we could go into but I will stop this essay for the time being here.

Related

Show all images in a view "details" in ASP.NET MVC?

I try to show all my images in my db but for some reason that I don't understand I can't... if someone can help me, I would appreciate it.... I'm new in ASP.NET MVC, I know a few things but that's all
My View Detail
<div class="row">
<div class="col-md-3">
<img src="#Url.Action("PaginasComics", "Comics", new {IdC = Model.Pages})" />
</div>
</div>
My view is not an IEnumerable<> view, I been thinking in create a partial view and put in this part but i dont know if will work and i dont know how...
Controller
public ActionResult PaginasComics(string IdPagina)
{
var PaginasC = db.Paginas.Where(x => x.IdPaginaC.ToString() == IdPagina).FirstOrDefault();
return File(PaginasC.Paginas, "imagen/jpeg", string.Format("{0}.jpg", IdPagina));
}
This is how I show the images, but is not working... I have the same code to "PORTADA" in model comics and it's work... I'm not really good work with arrays, maybe that's what I need.
Models
public class Comics
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int IdComics { get; set; }
public string Titulo { get; set; } //title
public byte[] Portada { get; set; } //cover
public DateTime FechadeEstreno { get; set; } //date release
public ICollection<PaginasComics> Pages { get; set; }
}
public class PaginasComics
{
public int IdPaginaC { get; set; }
public byte[] Paginas {get; set;}
public int Id_Comic { get; set; } //foreig key of comics
// here i save all the images
}
your url action, you passing IdC as parameters but your controllers asked for IdPagina parameter. try changes the url signature as requested in controller
<img src="#Url.Action("PaginasComics", "Comics", new {IdPagina = Model.Pages})" />
Issue was there you didn't use correctly qoutes in url.Action. And second action method parameter name should match with the parameter you defined in the url.action.
My View Detail
For getting multiple images from Action method and show in the View change this like:
#{
foreach(var pageId in Model.Pages){
<div class="row">
<div class="col-md-3">
<img src="#Url.Action('PaginasComics', 'Comics', new {IdPagina = pageId })" />
</div>
</div>
}
}
For getting single image from Action method and show in the View change this like:
#{
<div class="row">
<div class="col-md-3">
<img src="#Url.Action('PaginasComics', 'Comics', new {IdPagina = Model.Pages })" />
</div>
</div>
}

Model members value not updated

I'm new to asp.mvc and I'm trying to figure out why when I try to update values of a model through EditorFor helpers it won't send the updated value.
I have to follow a strict code convention and my parameters must start with a prefix (bln/str/int and so on)
If I leave the other parameters (permissionKey/stationKey/username) without the prefix, the values received are the updated one (the ones I need).
[HttpPost, ActionName("Save")]
public ActionResult Save(int intId, string permissionKey, string stationKey, string username)
{
var perm = _repository
.Get(row => row.Id == intId)
.First();
perm.PermissionKey = permissionKey;
perm.StationKey = stationKey;
perm.Username = username;
If I change the definition to strPermissionKey/strStationKey/strUsername, I will receive the old values of the model.
Model:
public class EditablePermissionRowModel
{
public int Id { get; set; }
public string PermissionKey { get; set; }
public string StationKey { get; set; }
public string Username { get; set; }
public bool EditPressed { get; set; } = false;
}
my Html looks like this:
#using (Html.BeginForm("Save", "Permissions"))
{
#*#Html.AntiForgeryToken()*#
#Html.HiddenFor(m => m.Id)
.... more html....
<button type="submit" class="btn btn-link">
<span class="glyphicon glyphicon-ok"></span>
</button>
I also tried to include all parameters that I need as hidden.
#Html.Hidden("strPermissionKey", Model.PermissionKey);
But this still didn't work. (Also tried #Html.Editor("strPermissionKey", Model.PermissionKey) but not luck)
What am I missing?
EDIT:
#StephenMuecke helped and pointed out that I only need to pass the model back as a parameter... and this did it. Problem solved.
this:
public ActionResult Save(EditablePermissionRowModel udtModel) {
... code ....

How to code an auction site with ASP.Net/ MVC 5 / EF

I'm relatively new to ASP.NET so please bear with me.
I'm trying to code a straightforward auction site for a charity, using MVC 5 and Entity Framework with Code-First.
I have created an Item model and controller. The Item model holds fields like the title, description, starting bid, current bid, number of bids, high bidder, etc.
public class Item
{
public int ID { get; set; }
public string Code { get; set; }
public string Title { get; set; }
public string Description { get; set; }
[DisplayName("Starting Bid")] public int StartingBid { get; set; }
public int Increment {get; set;}
public int Bids { get; set; }
[DisplayName("Current Bid")] public int CurrentBid { get; set; }
public string HighBidder { get; set; }
public int CurrentOrStartingBid
{ // The price that is displayed next to an item
get
{
return Bids > 0 ? CurrentBid : StartingBid;
}
}
public int NextBid
{ // The minimum amount that is valid for a new bid
get
{
return Bids > 0 ? CurrentBid + Increment : StartingBid;
}
}
}
(What I have tried to do in the code above is to add these properties CurrentOrStartingBid and NextBid which are not intended to be part of the database record, they are just derivative properties rather than columns in the DB. So hopefully having these as read-only properties will do that...)
I am now making a view for the item detail. This shows the item description, and also features form controls for placing a bid, similar to what you would see on eBay. My question is about how to wire up the logic for the Bid button correctly.
I figure that in the view I should use an HTML form with a submit button for the bidding. This does an HTTP post when the button is hit, and allows me to write a method in the Item controller that gets called at that time.
So my Razor code in the view currently looks like this:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="form-group">
<div class="input-group">
<span class="input-group-addon">£</span>
<input type="number" class="form-control" value="#Model.NextBid" id="CurrentBid" name="CurrentBid"/>
</div>
<br />
<button type="submit" class="btn btn-primary btn-lg">Place bid</button>
</div>
}
Using this code allows me to write a controller method with this signature:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Bid([Bind(Include = "ID,CurrentBid")] Item item)
This kind of works so far. But note that I am having to pass the amount that has been bid in the CurrentBid field of the item, before it has been validated server-side. This doesn't feel quite right to me. Is there a way of writing the method so it just takes a signature like this?
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Bid(int itemID, int bidAmount)
Maybe there's a way to do that with query strings or something?
Anyway, once inside the method, things again seem a little weird. Protecting from over-posting, the only fields in the item variable that are valid are ID and CurrentBid. So I then do a lookup in the DbContext to find the actual item that corresponds with that ID and update it:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Bid([Bind(Include = "ID,CurrentBid")] Item item)
{
if (ModelState.IsValid)
{
Item i2 = db.Items.Find(item.ID);
if (item.CurrentBid >= i2.NextBid)
{
i2.Bids++;
i2.CurrentBid = item.CurrentBid;
i2.HighBidder = HttpContext.User.Identity.Name;
db.Entry(i2).State = EntityState.Modified;
db.SaveChanges();
}
return View(i2);
}
return RedirectToAction("Auction");
}
This seems to work. But it does not feel right. I think I am missing some important concepts/patterns here. If any experienced MVC folks could sketch how they would wire up the client/server logic for this simple button, that would be great. (You would also be helping a good cause!)
Is there a way of writing the method so it just takes a signature like
this?
[HttpPost] [ValidateAntiForgeryToken]
public ActionResult Bid(int itemID, int bidAmount)
Sure there is a way, lets say you have a simple model like this -
public class Item
{
public int ID { get; set; }
public int NextBib { get; set; }
public int CurrentBid { get; set; }
public string Description { get; set; }
}
Then your Action is creating an Item and sending to View as shown below -
public ActionResult BidForm()
{
Item i = new Item();
i.ID = 100;
i.CurrentBid = 10;
return View(i);
}
And your view is as follows -
#model MVC.Controllers.Item
#{
ViewBag.Title = "BidForm";
}
<h2>BidForm</h2>
#using (Html.BeginForm("Bid", "sample", FormMethod.Post))
{
#Html.AntiForgeryToken()
<input type="hidden" name="itemId" value="#Model.ID" />
<input type="number" class="form-control" value="#Model.NextBib" id="CurrentBid" name="bidAmount" />
<input type="submit" value="Create" />
}
And your NextBid action would be -
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Bid(int itemID, int bidAmount)
{
return View();
}
As you see we have an HiddenField with name = itemID and a input type=number field with name = `bidAmount. Those are mapped to parameters of the action as shown below -
Some recommendations which you can consider -
Instead of using regular HTml.BeginForm(), you can go for Ajax.BeginForm() to give more intuitive user experience. Alternatively you can use JQuery POST operation too.
To prevent OVER posting, you need to create specific ViewModels for specific activities like increasing a bid, displaying a product etc. And you post only required viewModels for required activities, so that you can prevent over posting of form.

Dynamic link issue in an MVC 3 website

I'm creating my first ASP.NET MVC 3 website for my company's intranet. It's a pretty cool, I play audio recorded by our phone system and saved in our db. That's working good, but I'm having a hard time figuring out how to do something that should be simple. Please forgive any syntax errors I most likely have, this is a rough draft.
I have a table in the Index View /Apps that list all the AppName's, and next to each AppName I want to display a link to another view, with the text of the link being a Count() of all CallDetails associated with that App.
I have two classes:
public class Apps
{
public int AppId { get; set; }
public string AppName { get; set; }
}
public class CallDetail
{
public int Id { get; set; }
public int AppID { get; set; }
public byte[] FirstName { get; set; }
public byte[] LastName { get; set; }
....etc
}
a context for each:
public class AppsContext : DbContext
{
public DbSet<Apps> Apps { get; set; }
}
public class CallContext : DbContext
{
public DbSet<CallDetail> CallDetails { get; set; }
}
a controller method for each:
// AppsController
private AppsContext db = new AppsContext();
public ViewResult Index()
{
return View(db.Apps.ToList());
}
// CallController method (from my current attempt)
public ActionResult CallCheck(int id)
{
bool? enabled = null;
var appcalls = from s in db.CallDetails
where s.AppID == id
&& s.Enabled.Equals(enabled)
select s;
string callnum = appcalls.Count().ToString();
return View(callnum);
}
It displays the AppName just fine in this portion of the View below, and I can create a link to a View for each associated CallDetail just fine. But I don't know how to display info I'd get from the CallDetail Controller since the View's Model is Apps and its Controller, AppsController.
#model IEnumerable<myMessagePlayer.Models.Apps>
...
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.AppName)
</td>
<td class="appLink">
...
</td>
</tr>
}
I've tried many different methods, some that I might have gotten to work, but they seemed semantically un-MVC. So I figured I'd just ask a general "whats the standard practice?" type of question.
The path you are currently going down would end up hitting the database for each app you have in your database. There is a way to display all the information with only one hit to the database.
Your context needs to change to this:
public class ApplicationContext : DbContext
{
public DbSet<Apps> Apps { get; set; }
public DbSet<CallDetail> CallDetails { get; set; }
}
You could create a view model object called AppCallInfo that has three properties:
public class AppCallInfo
{
public int AppID { get; set; }
public string AppName { get; set; }
public int CallCount { get; set; }
}
In your Index action you need to do something like this:
public ViewResult Index()
{
var model = from a in db.Apps
join c in db.CallDetails on a.AppID equals c.AppID
where c.Enabled == enabled
group a by new { AppName = a.AppName, AppID = a.AppID } into g
select new AppCallInfo {
AppName = g.Key.AppName,
AppID = g.Key.AppID,
CallCount = g.Count()
};
return View(model.ToList());
}
Now you have everything you need for each row in your table in one object.
#model List<myMessagePlayer.ViewModels.AppCallInfo>
...
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.AppName)
</td>
<td class="appLink">
#Html.ActionLink(item.CallCount, "ViewCalls", "Call", new { Id = item.AppID }, null)
</td>
</tr>
}
Using this method avoids hitting the database for each app you have in your table.
Is the view CallCheck a partial view?
In your index view you could use
#Html.RenderAction("CallCheck", "AppsController", new { Id = #Model.AppId } )
The syntax may not be 100% correct, but it should get you going in the right direction.

ASP.NET MVC how to achieve to use the same model with different error message

I am having this issue at the moment, I had address model (use required attribute to decorate) which can be used more than once on the same page, one is billing address and the other one is shipping address. when validation failed, I'd like to have suffix in front of my generic error message indicate which address is required e.g. "{0} - address line 1 required", either billing or shipping
Here is my model
public class AddressBaseModel
{
[Display(Name="Address line 1")]
[Required(ErrorMessageResourceType = typeof(ModelValidation), ErrorMessageResourceName = "AddrLine1Required")]
public string AddressLine1 { get; set; }
[Display(Name="Address line 2")]
[Required(ErrorMessageResourceType = typeof(ModelValidation), ErrorMessageResourceName = "AddrLine2Required")]
public string AddressLine2 { get; set; }
[Display(Name="Address line 3")]
public string AddressLine3 { get; set; }
[Display(Name="Address line 4")]
public string AddressLine4 { get; set; }
}
}
Here is the code segment I used in my page
<fieldset class="space-bottom">
<legend>Please enter your home address</legend>
<div id="home_fields">
#Html.EditorFor(m => m.HomeAddress)
</div>
</fieldset>
<fieldset class="space-bottom">
<legend>Please enter your delivery address</legend>
<div id="delivery_fields">
#Html.EditorFor(m => m.DeliveryAddress)
</div>
</fieldset>
Thanks
Personally I use the FluentValidation.NET library instead of Data Annotations as it makes things so much easier and provides a lot more power. Here's an example of how to achieve your goal using this ilbrary.
Create a new ASP.NET MVC 3 project using the default Visual Studio template
Install the FluentValidation.MVC3 NuGet package.
Add the following line to Application_Start:
ModelValidatorProviders.Providers.Add(
new FluentValidationModelValidatorProvider(
new AttributedValidatorFactory()
)
);
Define the following models:
public class AddressBaseModel
{
public string AddressLine1 { get; set; }
}
[Validator(typeof(MyViewModelValidator))]
public class MyViewModel
{
public AddressBaseModel HomeAddress { get; set; }
public AddressBaseModel DeliveryAddress { get; set; }
}
And the following Validators:
public class AddressBaseModelValidator : AbstractValidator<AddressBaseModel>
{
private readonly string _addressType;
public AddressBaseModelValidator(string addressType)
{
_addressType = addressType;
RuleFor(x => x.AddressLine1)
.NotEmpty()
.WithMessage(string.Format("{0} - address line 1 required", addressType));
}
}
public class MyViewModelValidator : AbstractValidator<MyViewModel>
{
public MyViewModelValidator()
{
RuleFor(x => x.HomeAddress)
.SetValidator(new AddressBaseModelValidator("billing"));
RuleFor(x => x.DeliveryAddress)
.SetValidator(new AddressBaseModelValidator("shipping"));
}
}
Modify the HomeController:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
HomeAddress = new AddressBaseModel(),
DeliveryAddress = new AddressBaseModel()
};
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
And the corresponding Index.cshtml view:
#model MyViewModel
#using (Html.BeginForm())
{
<fieldset class="space-bottom">
<legend>Please enter your home address</legend>
<div id="home_fields">
#Html.EditorFor(m => m.HomeAddress)
</div>
</fieldset>
<fieldset class="space-bottom">
<legend>Please enter your delivery address</legend>
<div id="delivery_fields">
#Html.EditorFor(m => m.DeliveryAddress)
</div>
</fieldset>
<input type="submit" value="Register" />
}
You could create a custom attribute that does the dynamic formatting for you. You would just tag your address fields with the Address attribute like this:
[Address]
public string AddressLine1 { get; set; }
You would need to add a property in the AddressBaseModel where you tell the system what type of address this is (you would set this to "Billing" or "Shipping" when you instantiate the view model right before you pass the view model to the View in the controller get action):
public string AddressType { get; set; }
A custom attribute like this should work (I haven't tested it, I wrote it just now). This automatically gets the address type you specified when you create the model instance and formats it with the display name of the address field).
public class AddressAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "{0} - {1} required";
public AddressAttribute()
: base(DefaultErrorMessage) { }
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value != null)
{
if (!base.IsValid(value))
{
// get the property called "AddressType" from the model so we know if it's Billing or Shipping
var addressType = validationContext.ObjectInstance.GetType()
.GetProperty("AddressType")
.GetValue(validationContext.ObjectInstance, null);
// use the display name of the address field in the error message
return new ValidationResult(
string.Format(DefaultErrorMessage, addressType, validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
}
This should work:
[Required(ErrorMessage = "The Address 2 is required.")]

Resources