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!
Related
I have a slickgrid and am attempting to save its data back to the server.
When I breakpoint on the server, I can see the data in the Request.Form object, but I can't make it work with my object.
My data looks like...
[
{"id":"0","LineNumber":"","Detail":"MOT cost","Code":" ","Qty":"1","Est":" ","CustomerDamage":false,"Cost":"44.00","Value":"44.00","VAT":true,"SelfBillingLine":"False","DefectStatus":" "},
{"id":"62","LineNumber":"","Detail":"CRACKS IN Chassis","Code":"TLMA02","Qty":"1","Est":"","CustomerDamage":false,"Cost":"35.00","Value":"35.00","VAT":true,"SelfBillingLine":"False","DefectStatus":"Large repair"},
{"id":"63","LineNumber":"","Detail":"TEAR IN N/S CURTAIN","Code":"TLMA02","Qty":"1","Est":"","CustomerDamage":true,"Cost":"10.00","Value":"10.00","VAT":true,"SelfBillingLine":"False","DefectStatus":"Customer"}
]
I am posting with a button onclick...
$("#SBSave").click(function() {
debugger;
var details = JSON.stringify(defectrows);
save('SBDetail/SaveSBItem', details);
});
I have tried a number of things to receive the data, none of them work.
My controller...
[HttpPost]
public void SaveSBItem(SelfBillDetailList details, string Approve = "")
{
// Actions here.
}
My model...
Trying a number of things, neither work...
public class SelfBillDetailList
{
public IEnumerable<SelfBillingIncomingDetail> IncomingDetails { get; set; }
}
public class SelfBillingIncomingDetail
{
public int id { get; set; }
public string Code { get; set; }
public string LineNumber { get; set; }
public string Detail { get; set; }
public string Action { get; set; }
public string Qty { get; set; }
public string Est { get; set; }
public bool VAT { get; set; }
public bool CustomerDamage { get; set; }
public string Cost { get; set; }
public string Value { get; set; }
public DateTime Received { get; set; }
public string DefectStatus { get; set; }
public bool SelfBillingLine { get; set; }
}
So, I have tried an individual SelfBillingIncomingDetail and also a the SelfBillDetailList.
Neither work.
I have even sent an individual row, again, neither work.
I want to send it as a group, so it will be an array of SelfBillingIncomingDetail but nothing works.
Thank you for your help.
I have done it again... eventually found an answer after looking for ages.
Darin Dimitrov's answer in
Post an Array of Objects via JSON to ASP.Net MVC3
let me to the answer.
It seems that when sending the data, I need to give the array of data the same name as the property name in SelfBillDetailList, so...
var details = JSON.stringify({IncomingDetails : defectrows});
fixes it.
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'm trying to expand beyond the "one-to-one" mapping of models to controllers to views that most mvc(3) tutorials offer.
I have models for a person (operator) and the person's picture. In the database they would correspond to Operator and OperatorPicture tables.
public class Operator
{
public Operator()
{
this.OperatorPictures = new HashSet<OperatorPicture>();
}
[DisplayName("Operator ID")]
public int OperatorID { get; set; }
[Required]
[DisplayName("First name")]
[StringLength(20, ErrorMessage = "The first name must be 20 characters or less.")]
public string OperatorFirstName { get; set; }
[Required]
[DisplayName("Last name")]
[StringLength(20, ErrorMessage = "The last name must be 20 characters or less.")]
public string OperatorLastName { get; set; }
public virtual ICollection<OperatorPicture> OperatorPictures { get; set; } // Nav
}
public class OperatorPicture
{
[DisplayName("Operator Picture ID")]
public int OperatorPictureID { get; set; }
[DisplayName("Operator ID")]
public int OperatorID { get; set; }
[DisplayName("Picture")]
public byte[] Picture { get; set; } // 100x100
[DisplayName("Thumbnail")]
public byte[] Thumbnail { get; set; } // 25x25
public virtual Operator theoperator { get; set; } // FK
}
In most views I would present them together. A list of operators would include a thumbnail picture if it exists. Another screen might show the person's detailed information along witht the full-sized picture.
Is this where viewmodels come into play? What is an appropriate way to use them?
Thanks,
Loyd
Definitely a time for using them. ViewModels are just a way of packaging up all the different bits that go into a view or partialview.
Rather than have just the repository pattern, I have repositories and services. The service has a Get property called ViewModel, that can be called from the controller. EG:
// initialise the service, then...
return View(service.ViewModel)
You can see a more detailed description of my approach in the accepted answer for the following SO question:
MVC Patterns
Note that I am using dependency injection in that code.
It may not be a totally orthodox or canonical approach, but it results in very clean controller code and a easy to use pattern.
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.
I'm having trouble passing view information from my Get/Create action to my view. Here are my three model classes;
public class Competition
{
public int Id { get; set; }
public int CompetitionId { get; set; }
public string Name { get; set; }
public string Prize { get; set; }
}
public class CompetitionEntry
{
public int Id { get; set; }
public int CompetitionEntryId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int CompetitionId { get; set; }
}
public class CompetitionEntryViewModel
{
public int Id { get; set; }
public Competition Competitions { get; set; }
public int CompetitionId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
Here is my Get/Create action in CompetitionEntry Controller;
public ActionResult Create(int id)
{
CompetitionEntryViewModel competitionentryviewmodel = db.CompetitionEntriesView.Find(id);
return View(competitionentryviewmodel);
}
I know this doesn't work. The id parameter goes into the URL fine. How to I get access to my Competition class in th Get action? I need to be able to show the competion name on my Create Competition entry view.
Thanks in advance!
public ActionResult Create(int id)
{
var data = db.CompetitionEntriesView.Find(id);
CompetitionEntryViewModel competitionentryviewmodel = new CompetitionEntryViewModel();
competitionentryviewmodel.CompetitionName = data.Name;
return View(competitionentryviewmodel);
}
What you are trying to do is build an object graph and display it through a view model. In order to do this, you need to map your domain model(s) to your view model.
You can do the mapping yourself by writing a lot of code (re-inventing the wheel), or, you could consider using third party tools to do this for you. I recommend you use an AutoMapper as it is very simple to use imo.
The other problem is that your view model contains a domain model. This is likely to cause you a lot of headache in near future. If I were you, I would replace Competition with CompetitionViewModel.
I would also consider creating a view model for a list of competitions, i.e. CompetitionsViewModel. Look into partial views to see how you can display a list of competitions.
Good luck