I my view model(LIST) looks like this:
public class ConversationModel
{
public int ID { get; set; }
public string Body { get; set; }
public DateTime Datetime { get; set; }
public string Username { get; set; }
public string ImgUrl { get; set; }
public string ToUserID{ get; set; }
}
this is my view
#model IEnumerable<NGGmvc.Models.ConversationModel>
how i can get ToUserID on current postion? something like this
#Model[0].ToUserID
Thanks
You should be able to do:
#Model.First().ToUserID
Note, you may want to check whether there are any items in the enumerable first as if there aren't, then First() will return null
(Note, because you had #Model[0] in your question, I assume you are specifically trying to get the first value. I may have the wrong end of the stick, in which case Jakub's answer should sort you out!)
You should be able to use the following:
#Model.First().ToUserID
However, if your model will only ever reference the first element of the enumeration in the view, I would recommend that you only pass that element to the view.
For example:
#model ConversationModel
#Model.ToUserID
And in the controller only pass the first element that is required:
List<ConversationModel> conversationList = //your conversation model initialisation code
return View(conversationList.First());
#foreach(var model in Model)
{
#model.ToUserID
}
Related
I am newbie to MVC3 and I wonder if this is even possible and good practice?
I have a model + view + controller which works fine. This view shows a list of people - I want to be able to click on a person's name and be redirected to a new view that will show that persons details. This new view only has a ViewModel, but no controller because I plan to pass in the object in the action.
The Person object contains all the properties my view needs to show:
#Html.ActionLink(item.Person.FirstName, "PersonDetails", item.Person)
Is this possible/good practice??
I believe you have an misunderstanding of how MVC works. Your ActionLink will ALWAYS redirect to a corresponding ActionMethod of a Controller. What you'll want to do is create an action method in your controller that accepts the necessary parameters and then returns to the View your ViewModel.
Here is a very quick example to get you started:
public class HomeController : Controller
{
public ActionResult List()
{
return View();
}
public ActionResult DetailById(int i)
{
// load person from data source by id = i
// build PersonDetailViewModel from data returned
return View("PersonDetails", PersonDetailViewModel);
}
public ActionResult DetailByVals(string FirstName, Person person)
{
// build PersonDetailViewModel manually from data passed in
// you may have to work through some binding issues here with Person
return View("PersonDetails", PersonDetailViewModel);
}
}
Not a good way to do it like you want to (in your original post). A view should always have a view model. A view model represents only the data that you want to have on the view, nothing more and nothing less. Do not pass your domail model to the view, but rather use a view model. This view model might contain just a portain of the properties of your domain model.
In your list view you probably have a grid, and next to each row you probably have a details link, or a link on the name (as you have it). When either of these links are clicked then you are directed to a details view. This details view will have its own view model with only the properties that you need to display on the details view.
A domail model might look something like:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string ExampleProperty1 { get; set; }
public string ExampleProperty2 { get; set; }
public string ExampleProperty3 { get; set; }
}
Let say you only want to display the person's id, first name, last name and age then your view model will look like this:
public class PersonDetailsViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
You don't need ExampleProperty1, ExampleProperty2 and ExampleProperty3 because they are not required.
Your person controller might look like this:
public class PersonController : Controller
{
private readonly IPersonRepository personRepository;
public PersonController(IPersonRepository personRepository)
{
// Check that personRepository is not null
this.personRepository = personRepository;
}
public ActionResult Details(int id)
{
// Check that id is not 0 or less than 0
Person person = personRepository.GetById(id);
// Now that you have your person, do a mapping from domain model to view model
// I use AutoMapper for all my mappings
PersonDetailsViewModel viewModel = Mapper.Map<PersonDetailsViewModel>(person);
return View(viewModel);
}
}
I hope this clears things up a little more.
Here is view models
public class ArticleViewModel
{
public string ID { get; set; }
public string Title{ get; set; }
public string Body { get; set; }
public List<BETag> TagsList { get; set; }
}
public class BETag
{
public string ID { get; set; }
public string Name { get; set; }
}
An action
[HttpPost, AuthorizeEx]
public ActionResult AddArticle(ArticleViewModel articleViewModel)
{
//articleViewModel.Tags.Count == 0
//Request.Form["TagsList"] == "tag1, tag2..."
}
and a part of AddArticle.cshtml
#Html.TextAreaFor(m => m.TagsList )
My question is why articleViewModel.Tags.Count is equal 0, but
Request.Form["TagsList"] is equal "tag1, tag2..."? How to bind ArticleViewModel properly?
Lists don't work that way in MVC. You need to use something like EditorFor(m => m.TagsList) and then you need to create a BETag EditorTemplate. But that's only part of the problem, and really won't work for you either.
What you really want is just a simple string that takes your list of tags, such as
public string TagListString {get;set;}
Then, in your controller, you parse the string and extract all your tags, then add them to the TagsList.
var tags = TagListString.Split(' '); // assumes only single space between tags,
// you should add more filtering to make sure
foreach(var tag in tags) {
TagList.Add(new BETag() { Name = tag });
}
MVC works with single items, not complex types. There is some built-in processing to breakdown complex types in some cases, and to automatically iterate over collections, but those don't work in your case because you want to edit all the items in a single field. So your only option is to parse the field in the post method and put the data where you want it.
I have a class Article:
public class Article
{
public int Id { get; set; }
public string Text { get; set; }
public Title Title { get; set; }
}
And Title:
public class Title
{
public int Id { get; set; }
public string Name { get; set; }
public int MaxChar { get; set; }
}
Before you can write an Article, you have to choose your Title from a list, so your StringLength for Article.Text can be determined. Meaning, this article can only have a certain amount of chars, deppending on what 'Title' the writer has. Example: Title.Name "Title1" can only write an article with 1000 chars (MaxChar), and Title.Name "Title2" can write an article with 3000 chars. So. Thats means the the string length for Article.Text has to come from Title.MaxChar.
The Title entity is prefixed data that will be stored in the db.
Here's what ive done sone far:
The titles from the db are listed in a view, with a link to create action of the ArticleController with a "title" querystring:
#Models.Title
#foreach (var item in Model) {
#Html.ActionLink(item.Name, "Create", "Article", new { title = item.Id}, new FormMethod())
}
You fill the form, and post it. The HttpPost Create action:
[HttpPost]
public ActionResult Create(Article article)
{
if (article.Text.Length > article.Title.MaxChar)
{
ModelState.AddModelError("Text",
string.Format("The text must be less than {0} chars bla bla", article.Title.MaxChar));
}
if (ModelState.IsValid)
{
db.Article.Add(article);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(hb);
}
Here's the issue. The controller also adds a new Title entity. So the next time I navigate to the view where I have to choose Title, there's a duplicate of the last entity I used to write an article.
Should I do this in an entirly new way, or is there a small tweak. Only other thing I can think of, is just sending the MaxChar as a querystring, and have no relations between the models at all. Just seems a bit silly/webforms kindda.
Cheers
UPDATE #1:
Maybe im doing this the wrong way?
Get Create action
public ActionResult Create(int title)
{
var model = new Article
{
Title = db.Title.Find(title)
};
return View(model);
}
Or maybe its in the Model? Like, do I have to set foreign keys? Something like:
[ForeignKey("Title")]
public int MaxChar { get; set; }
public virtual Title Title { get; set; }
But im pretty sure I read some where that it isnt necesary, that EF takes care of that.
Easiest way would probably be to attach the title to the context in your Create action:
// ...
if (ModelState.IsValid)
{
db.Titles.Attach(article.Title);
db.Article.Add(article);
db.SaveChanges();
return RedirectToAction("Index");
}
// ...
Attach tells EF that article.Title already exists in the database, thereby avoiding that a new Title is inserted when you add the article to the context.
You need to have a distinction between your MVC model and your Entities model. Your MVC Article model should look something like this (bear in mind there are some religious debates about what goes into a model):
public class Article
{
public int Id { get; set; }
public string Text { get; set; }
public int TitleID { get; set; }
public IEnumerable<Title> AvailableTitles {get;set;}
}
In your view, you can create a dropdown based off the available titles, and bind it to the TitleID property. The list of available titles would be populated in the parameterless controller method (and the model-bound method as well).
When your model-bound method brings back the TitleID, instantiate the Title object from the Entities framework based off the ID. Create your Entities Article object using that Title object, and save your changes. This should get you where you want to be.
UPDATE #3: Entire question
I have a class HB:
public class HB
{
public int Id { get; set; }
[StringLength(3000)]
public string Text { get; set; }
public Title Title { get; set; }
}
And Title:
public class Title
{
public int Id { get; set; }
public string Name { get; set; }
public int MaxChar { get; set; }
}
Before you can write a HB (which is kind of an article), you have to choose your title, so your StringLength for HB.Text can be determined. Meaning, this article can only have a certain amount of chars, deppending on what 'Title' the writer has. Example: Title1 can only write a 'HB' with 1000 chars, and Title2 can write a 'HB' with 3000 chars. So. Thats means the the StringLength has to come from Title.MaxChar. Whats the smartest way to do that?
The Title entity is prefixed data that will be stored in the db.
To be crystal clear, what I want to achieve is something in the line with: [StringLength(Title.MaxChar)]
Ive done structure/design for this mechanism in Webforms a million times, my brain just cant addapt to mvc, so some help would be appreciated. Code would be even more appreciated.
Pretty sure that is not possible as written. This strikes me as trying to force business logic into the model that belongs in the controller.
In this situation, I would make the attribute on the Text property [StringLength(3000)]. In the controller, during validation, I would write something along these lines:
public ActionResult (HB model)
{
if (model.Text.Length > model.Title.MaxChar){
ModelState.AddModelError("Text", string.Format("Text for this Title cannot exceed {0} characters.", model.Title.MaxChar));
}
if (ModelState.IsValid)
{
//do stuff
return RedirectToAction("Index"); //or something
}
else
{
return View(model);
}
}
I believe this will accomplish what you are trying to do. Now, for the Title object, I'd flatten that out a bit in your model:
public class HB
{
#region Base Properties
public int Id { get; set; }
[StringLength(3000)]
public string Text { get; set; }
#endregion
#region Title Properties
public int TitleId { get; set; }
public string TitleName { get; set; }
public int TitleMaxChar { get; set; }
#endregion
}
This is assuming you need to display that information in your view. If you just need to reference it for your business logic validation, just have the TitleId property and use that to instantiate the Title object in your controller when you need it. Don't forget to make hidden inputs for each of these properties if they are not editable!
I have this model:
namespace easyBooking.Models
{
public class CardInfo
{
public string name { get; set; }
public string adress { get; set; }
public string zipcode { get; set; }
public string city { get; set; }
public string ccsID { get; set; }
public string birthday { get; set; }
public string nationalitet { get; set; }
public string cardType { get; set; }
}
}
which I populate from a function and return to the controller. The problem is that I cannot hand this model to my view, since it does not implement IEnumerable... Instead I have to add information to the viewBag like this:
var cardInfo = FunctionLib.cardCampingPas(myArray);
ViewData.Add("name", cardInfo.name);
ViewData.Add("adress", cardInfo.adress);
ViewData.Add("nationalitet", cardInfo.nationalitet);
ViewData.Add("ccsID", cardInfo.ccsID);
ViewData.Add("zipcode", cardInfo.zipcode);
ViewData.Add("city", cardInfo.city);
ViewData.Add("cardType", cardInfo.cardType);
return View("../Reservation/New", ViewData);
which is kind of stupid, when I should be able to just add the cardInfo model directly to the View.
var cardInfo = FunctionLib.cardCampingPas(myArray);
return View("../Reservation/New", cardInfo);
So basically I just need to pass a bunch of strings to the view. How can my model CardInfo be accessed directly from my view?
Update
#rene - view data where I added a #model declaration. I did get some errors when I tried it last, but it must have been because I tried to foreach it or something.. now... just no error, and I have no idea if anything is passed to the view or how to get the data.
#model easyBooking.Models.CardInfo
#{
ViewBag.Title = "New";
}
<h2>Ny reservation</h2>
<p>Benyttet kort: #Model.cardType</p>
This now works...
You can pass your collection directly to the view.
In the view, you'll need to declare the model type:
#model IEnumerable<CardInfo>