I'm creating an internal CMS system. I have a page that retrieves an Item and allows the user to carry our various administrative tasks.
Lets look at the following:
Quote (Page)
Item (Page) - ItemDetails (Partial within Item) - Artwork (partial within ItemDetails)
A user selects a quote. Then views an Item which retrieves an item with associated content. Within an Item, they can select Artwork or any other tabs (partial views also).
The user then has the ability to change values for an internal CMS system. I want to:
Pre-populate several different dropdown lists. (If it helps, I have some pre-formatted select tags, I could re-use, was used in an angular project).
These Dropdown boxes should be pre-loaded with any existing values if found attached to an item.
Have a form with the ability to change the value and update it.
Having just moved to MVC, I'm struggling to nail it, despite scouring for over a day on various guides/tutorials. Just in case you're wondering why I'm wanting to separate the controllers, it's because the project is going to become vast. Architecturally, it makes sense.
Example: Mockup Types is within Artwork. I've used entity framework and I'm retrieving all my MockupTypes successfully.
The first partial:
#Html.Partial(MVC.Item.Views.ViewNames._TabbedItemDetailsPanel, Model.SelectedItem)
SelectedItem viewModel:
public class SelectedItem
{
public ArtworkDetailViewModel ArtworkDetail { get; set; }
//Several others
}
ArtworkDetail contains:
public class ArtworkDetailViewModel : DataViewModel
{
public int? ItemID { get; set; }
public FaceColourViewModel FaceColour { get; set; }
public MockupTypeViewModel MockupType { get; set; }
public ProofingTypeViewModel ProofType { get; set; }
//Others
}
This is brought in by the item controller:
public partial class ItemController : Controller
{
public virtual ActionResult ItemDetail(int itemId)
{
var model = new QuotePageViewModel();
var item = itemViewModelService.GetItemByID(itemId);
model.LiveQuotes = quotesOverviewViewModelService.GetItemsByQuote(item.QuoteID.Value);
model.SelectedItem = item;
return View(model);
}
}
Within the partial posted above, I have a tab for Artwork, which is another separate partial view:
#Html.Partial(MVC.Item.Views._TabbedArtwork, new ViewModels.Artwork.ArtworkAttributesViewModel())
My thinking is the ArtworkAttributesViewModel will have a list of pre-populated models
public class ArtworkAttributesViewModel
{
public IEnumerable<ProofingTypeViewModel> ProofTypes { get; set; }
public IEnumerable<MockupTypeViewModel> MockupTypes { get; set; }
//Others
}
This comes from a separate controller called Artwork.
public partial class ArtworkDetailController : Controller
{
[HttpGet]
public virtual ActionResult GetAttributes()
{
var model = new ArtworkAttributesViewModel();
model = artworkAttributesViewModelService.GetArtworkAttributes();
return View(model);
}
}
Within _TabbedArtwork, I started to create a form:
#model ViewModels.Artwork.ArtworkAttributesViewModel
#using (Html.BeginForm(MVC.ArtworkDetail.ActionNames.Index, MVC.ArtworkDetail.Name, FormMethod.Get))
{
<div class="row">
<div class="col-md-12">
<p><strong></strong> <br />
</div>
<div class="col-md-12">
<div class="row show-grid">
<p class="margin-bottom-zero"><strong>Mockup Types</strong></p>
<div class="col-md-3 col-xs-12">
#Html.DropDownListFor()
<select class="selectpicker"></select>
</div>
</div>
</div>
</div>
}
In summary, I have 2 partials, 2 viewmodels and 2 controllers. How on earth do I bring all of this this together?
Much appreciated.
Edit: Still battling on getting the second controller to send the view to the partial
You can bind the dropdown to the item to be edited and the model to populate it.
public class mainitem() {
public int id { get; set; }
public int type { get; set; }
}
public class type() {
public int id { get; set; }
public string name { get; set; }
}
public class myviewmodel() {
public mainitem item { get; set; }
public List<type> types { get; set; }
}
#Html.DropDownListFor(x => x.item.type,
new SelectList(Model.types, "id", "name"), "-Select-",
new { #class = "form-control" })
Related
I've been searching all over the web to find an answer for such simple question, but I can't seem to find one.
Suppose we have a Product that has an edit page where we can set name, price and category.
Following the MVC(MVVM) pattern we have:
2 models ProductModel and ProductCategoryModel
ViewModel that holds the product and the categories list (for the dropdown)
View to display the edit page
Beside the above we have the ProductService which handles the CRUD operations
// Model
public class ProductModel
{
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int CategoryId { get; set; }
}
// Model
public class ProductCategoryModel
{
public int CategoryId { get; set; }
public string Name { get; set; }
}
// ViewModel
public class EditProductViewModel
{
public ProductModel Product { get; set; }
public List<ProductCategoryModel> Categories { get; set; }
}
The question is who is responsible for populating the ViewModel? I though it could be done in the constructor of the view model, however people say it is bad practice. Populating it in the controller also doesn't seems right.
You will map your EditProductViewModel from your ProductModel/ProductCategoryModel in the page controller.
You can then call your action to render your html page and pass your viewModel object.
Something like this :
public class HomeController : Controller
{
private EditProductViewModel viewModel;
public HomeController()
{
this.viewModel = new EditProductViewModel();
InitialiseViewModel();
}
public ActionResult Index()
{
return View("Index", viewModel);
}
private void InitialiseViewModel()
{
ProductCategoryModel productCategoryModel = new ProductCategoryModel();
ProductModel productModel = new ProductModel();
//do your mapping
productModel.Name = "Test mapping";
this.viewModel.Product = productModel;
}
}
Your .cshtml will look like this
#model WebApplication5.ViewModels.EditProductViewModel
<div class="jumbotron">
<h1>ASP.NET</h1>
<p>#Model.Product.Name</p>
<p class="lead">ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS and JavaScript.</p>
<p>Learn more ยป</p>
</div>
This question already has answers here:
Asp.Net MVC: Why is my view passing NULL models back to my controller?
(2 answers)
Closed 5 years ago.
I need to display a specific form in a view with a model. The model is like that:
This is the final object filled what I need :
public class ObjetTotal
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Numero { get; set; }
public string Value { get; set; }
}
I choose to cut the form into two differents parts :
First a "static" part where the user can put common values for the differents ObjetsTotal.
Second a "variable" part where the user put differents values for the differents ObjetsTotal.
The final aim is that the user doest'n have to type, the same thing for all the objects ObjetTotal.
So, I create other objects (I don't know if it's a good practice) which represents the differents part of the form.
The static part with MainObjet and the variable part with Numbers. I put these two object into an other object "Mix" which contains one "MainObjet" and a list of "Numbers".
public class MainObjet
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class Numbers
{
public string Numero { get; set; }
public string Value { get; set; }
}
public class Mix
{
public MainObjet obj { get; set; }
public IEnumerable<Numbers> num { get; set; }
public Mix()
{
obj = new MainObjet();
num = new List<Numbers>();
}
}
Then I want to render the model Mix in a view to have the two parts of the form.
I've try this :
#model App.Models.Mix
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Mix</legend>
<h3>First Properties</h3>
<div>
#Html.TextBoxFor(model => model.obj.Id);
#Html.TextBoxFor(model => model.obj.Name);
#Html.TextBoxFor(model => model.obj.Description);
</div>
<div>
<table>
#for (int i = 0; i < 5; i++)
{
<tr>
<td>
#Html.TextBoxFor(model => model.num[i].Numero)
</td>
<td>
#Html.TextBoxFor(model => model.num[i].Value)
</td>
</tr>
}
</table>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
But after the submit I get an object Mix null in this ActionResult :
[HttpPost]
public ActionResult Test(Mix obj)
{
return View();
}
Can you explain me how to do that ? May I'm on a wrong way.
Don't consider the design of the form, and I don't know the right type to put to Numbers, Maybe a simple list be enough for that.
The thing I can see is that you are missing the initialization of your model properties in the parameter-less constructor. You should try to update your model code to be:
public class Mix
{
public MainObjet obj { get; set; }
public IEnumerable<Numbers> num { get; set; }
public Mix()
{
obj = new MainObjet();
num = new List<Numbers>();
}
}
As the model binder will instantiate your model, and it will find obj and num to null and will not be able to post the values back.
Hope this helps you.
I have 10 text type input controls, 5 on first tab & 5 on second tab on same page.
Now I have a class as
public partial class TravelerMaster
{
public virtual ICollection<TravelerDetail> TravelerDetails { get; set; }
}
which has a collection as TravelerDetail.
public partial class TravelerDetail
{
public byte PackageTypeId { get; set; }
public int? NoOfPackage { get; set; }
public decimal? Weight { get; set; }
public decimal? PricePerKg { get; set; }
public decimal? PricePerPackage { get; set; }
}
How do i bind the controls such that when I post the data, the TravelerMaster object has two objects in its collection.
Please help me. Thanks in advance. Feel free to ask any query.
You can submit a collection of a particular model like this. By applying an index (this can be anything unique but I've used an integer value for ease) you can submit a collection of the same model to the controller:
Form
<div id="tab1">
#Html.Hidden("data.Index","1")
#Html.TextBox("data[1].weight", "", new { #class = "your-class-names", #id = "data[1].weight" })
...other fields...
</div>
<div id="tab2">
#Html.Hidden("data.Index","2")
#Html.TextBox("data[2].weight", "", new { #class = "your-class-names", #id = "data[2].weight" })
...other fields...
</div>
Controller
[HttpPost]
public ActionResult YourControllerMethod(IEnumerable<TravelerDetail> data)
{
if (data==null || data.Count() == 0) throw new Exception("No Data Added");
....other validation...
foreach(var item in data) {
...database work...
}
}
I am relatively new to ASP.NET MVC and I find myself a bit on a stick wicket or rather, in a soup kinda situation. This is a length description so please bear with me. And I'm sorry if this seems a bit long and boring :). So, here's my problem :
This is my model.
public class SocialAccount
{
public int SocialAccountID { get; set; }
public int UserID { get; set; }
public int SocialSiteID { get; set; }
public string SocialAccountUsername { get; set; }
public string SocialAccountUserID { get; set; }
public string OAuthToken { get; set; }
public string Captcha { get; set; }
public string UserCaptchaValue { get; set; }
}
public class SocialSitePost
{
public int PostID { get; set; }
public string PostContent { get; set; }
public string PostAttachments { get; set; }
public List<SocialSite> SelectedSocialSites { get; set; }
public List<SocialAccount> SocialAccount { get; set; }
}
public class SocialSite : Audit
{
public int SocialSiteID { get; set; }
public string SocialSiteName { get; set; }
}
When a user is successfully authenticated, I store some user details in session and redirect to the home page. This is the code for that:
//action method in some Controller
public Login()
{
//Authenticate user
//Get all social account related information
SomeRepository someRepository = new SomeRepository();
List<SocialAccount> socialAccountDetails = someRepository.SomeMethod(user.UserID);
//Add social account related information to session
HttpHelper.StoreInSession("UserDetails", socialAccountDetails);
//redirect to HomePage
return RedirectToAction("HomePage", "Account");
}
In the HomePage action method, I retrieve the user object from session and check the registered social accounts. I also create a list object of SocialSite to maintain a list of all registered social sites. I then pack it in ViewData and send it to the view. This is the code for that:
//action method in some other controller
public ActionResult HomePage()
{
List<SocialSite> allSocialSites = null;
List<SocialAccount> userSocialAccountDetails = HttpHelper.RetrieveFromSession("UserSocialSiteDetails") as List<SocialAccount>;
if (userSocialAccountDetails != null && userSocialAccountDetails.Count != 0)
{
allSocialSites = new List<SocialSite>();
bool hasFacebookAccount = userSocialAccountDetails.Exists(x => x.SocialSiteID == Convert.ToInt32(CommonConstants.SocialSite.Facebook));
if (hasFacebookAccount)
{
allSocialSites.Add(new SocialSite() { SocialSiteID = 1, SocialSiteName = "Facebook" });
}
bool hasTwitterAccount = userSocialAccountDetails.Exists(x => x.SocialSiteID == Convert.ToInt32(CommonConstants.SocialSite.Twitter));
if (hasTwitterAccount)
{
allSocialSites.Add(new SocialSite() { SocialSiteID = 2, SocialSiteName = "Twitter" });
}
}
ViewData["SocialSites"] = allSocialSites;
return View();
}
I don't have a strongly-typed view. What I need to do is post the user response. So I create a form for POSTing. Now within this form, I want to display a checkbox for all the registered social accounts (ViewData would give me this info). So, lets say, if user A registered only a Facebook account, then only 1 checkbox representing Facebook should be visible. If user B registered a Facebook, Twitter and Instagram account, then 3 checkboxes should be visible one representing each of these social accounts. I guess you get the idea. This is my code for that:
#model SocialSitePost
#{
using (Html.BeginForm("ProcesssRequest", "Account", FormMethod.Post))
{
<div class="divProcessRequest">
<br />
<div>
#Html.TextAreaFor(model => model.PostContent)
</div>
<div>
#foreach (var socialSite in ViewData["SocialSites"] as List<SocialSite>)
{
#Html.CheckBox("SocialSiteID", new { value = socialSite.SocialSiteID, #checked = true });
#Html.Label(socialSite.SocialSiteName)
} //Tried this..And am able to display the checkbox
#*#for (int i = 0; i < Model.SelectedSocialSites.Count; i++)
{
#Html.CheckBoxFor("SocialSiteID", new { value = Model.SelectedSocialSites[i].SocialSiteID, #checked = true });
}*# //Tried this too..I know this shouldn't work and rightly so, it doesn't work :)
</div>
<br />
<div>
<input type="submit" id="btnPost" value="Post" />
</div>
</div>
}
}
Enough said!!! Now my real problem:
When the user selects any checkbox, I want to populate the SelectedSocialSites property of the SocialSitePost object. This is the model whose object the form is posting back. I need to be able to access the SelectedSocialSites property in the POST method. This is how my POST method is :
[HttpPost]
public ActionResult ProcessRequest(SocialSitePost post)
{
if (ModelState.IsValid)
{
List<SocialSite> allSocialSites = ViewData["SocialSites"] as List<SocialSite>; //Can't get anything here
int socialSiteNo = post.SelectedSocialSites.SocialSiteID; //This is what I want to be able to do
return View("HomePage");
}
}
Since, I am relatively new to ASP.NET MVC, I'm not really sure if this is the right way of doing this. I did try out a few things like EditorFor, trying to send the SocialSitePost object the first time the view is rendered (I don't want to do this as it doesn't make any logical sense).
Can someone tell me how I can get the SelectedSocialSites property of the SocialSitePost class populated in the POST method based on the checkbox selections made by the user?
Also, can someone tell me if I am doing something wrong here as I haven't found any questions here so far on SO which seem similar to this kind of situation?
The main problem here is that the model is not completely suitable for what needs to be displayed. That is, the format of the model with respect to the view and its representation at the backend is different. Hence, a ViewModel needs to be used here. This is what solved my issue.
These were the ViewModels I ended up designing:
public class SocialSiteViewModel : SocialSite
{
public bool IsSelected { get; set; }
public string SocialSitePostID { get; set; }
}
public class SocialSitePostViewModel : SocialSitePost
{
public List<SocialSiteViewModel> SocialSiteViewModel { get; set; }
}
And then I could easily use them on the view as such:
using (Html.BeginForm("ActionMethodName", "ControllerName", FormMethod.Post))
{
#Html.CheckBoxFor(x => x.SocialSiteViewModel[i].IsSelected)
#Html.HiddenFor(x => x.SocialSiteViewModel[i].SocialSiteID)
#Html.HiddenFor(x => x.SocialSiteViewModel[i].SocialSiteName)
}
In my view model, there is a list of element which contains a table of text inputs.
View model > List of element > Table > lots of text inputs
All of this is generated in the view via EditorTemplates.
Everything is bound perfectly to the view when it load (MVC is creating the right IDs and Names of each input. Now I want to add and delete rows and have the binding updated. This means, that all the right numbers are updated in the IDs and Name.
I have an idea of how to do it with pure JavaScript, but that would be very dirty and a pain to update when things change. I would like to do it with an HTML helpers or a model binder but haven't found a way yet!
Anyone have an idea of how to do that?
EDIT : apparently I have more chance to get answered if I post lots of code detail... but in this complex case, that's going to be long, so hang on!
Here is the view model. (Keep in mind that this code is a dumbed down version)
public class MVCViewModel
{
public List<ActivityElement> ActivityElementList { get; set; }
}
public abstract class ActivityElement
{
}
public class Step : ActivityElement
{
public Step()
{
this.Id = Guid.NewGuid();
this.Label = "Basic Step";
}
public Step(string label, AbstractInput input)
: this()
{
this.Label = label;
this.Input = input;
}
public Guid Id { get; set; }
public string Label { get; set; }
public AbstractInput Input { get; set; }
}
public abstract class AbstractInput
{
public object Value { get; set; }
}
public class GridInput : AbstractInput
{
public List<GridCell> Headerlist { get; set; }
public List<GridRow> RowList { get; set; }
}
public class GridRow
{
public List<GridCell> CellList { get; set; }
}
public class GridCell
{
public string ColumnName { get; set; }
public AbstractInput Input { get; set; }
}
public class TextInput: AbstractInput
{
public TextInput(string value)
{
this.Value = value;
}
}
Alright! If somehow you are still with me, that's good! I have put them in the order that they will be used in this particular case.
To dumb this down, the view model contains a list of Step. The first step contains a GridInput as his AbstractInput property. That grid contains rows and each rows contains cells. For this case, let say each cell contains a TextInput as his AbstractInput.
Now, on the view, when this is displayed with EditorTemplates and HtmlHelpers, all the IDs and Names of the inputs are good (therefor the binding is good), as shown here in this HTML output :
<td>
<input id="ActivityElementList_0__Input_RowList_0__CellList_0__Input_ModelType" name="ActivityElementList[0].Input.RowList[0].CellList[0].Input.ModelType" type="hidden" value="knockout_binding_prototype.Models.TextInput">
<input id="ActivityElementList_0__Input_RowList_0__CellList_0__Input_Value" name="ActivityElementList[0].Input.RowList[0].CellList[0].Input.Value" type="text" value="cell 1 row 1">
</td>
<td>
<input id="ActivityElementList_0__Input_RowList_0__CellList_1__Input_ModelType" name="ActivityElementList[0].Input.RowList[0].CellList[1].Input.ModelType" type="hidden" value="knockout_binding_prototype.Models.TextInput">
<input id="ActivityElementList_0__Input_RowList_0__CellList_1__Input_Value" name="ActivityElementList[0].Input.RowList[0].CellList[1].Input.Value" type="text" value="cell 2 row 1">
</td>
What I am trying to do here, is to be able to add and delete row to this table. This is fairly easy... The hard part is making sure all the binding (IDs and Names) are still good. In the case of this particular HTML output, if I delete the first row, I need the IDs and Names of the second row, to become equivalent to the first row IDs and Name.
I have been trying to find the most generic way to do it, and to try and make it JavaScript less as possible. All ideas will be pondered, so don't afraid to say something!
EDIT 2 : OK, as far as deleting rows go, I decided to just hide the delete row and flag it as deleted, so all the biding remains good. Adding row is simple in JavaScript, but I am searching for a way to do it without JavaScript (or only a little bit).
Here is the update in the view model for the delete flag.
public class GridRow
{
public GridRow()
{
IsDeleted = false;
}
public List<GridCell> CellList { get; set; }
public bool IsDeleted { get; set; }
}
Try this way without buggy JS html modifications. You can do delete this way as well. Also you can decide if you want to save the model on every add/delete call or save it after all changes.
Models:
public class SampleModel
{
public List<ItemModel> Items { get; set; }
public SampleModel()
{
Items = new List<ItemModel>();
}
}
public class ItemModel
{
public int Id { get; set; }
public string Name { get; set; }
}
Controller:
[HttpPost]
public ActionResult Add(SampleModel model)
{
model.Items.Add(new ItemModel { Name = "New Item" });
return PartialView("Items");
}
public ActionResult Index()
{
var model = new SampleModel(); // Or get model
return View(model);
}
[HttpPost]
public ActionResult Index(SampleModel model)
{
// Save model to DB
return RedirectToAction("Index");
}
Index.cshtml
#model SampleModel
#using (Html.BeginForm())
{
<div id="container">
#Html.Partial("Items", Model)
</div>
<a id="add" href="javascript:void(0);">Add</a>
<button type="submit">Save</button>
}
<script src="jquery.js"></script>
<script type="text/javascript">
$(function () {
$('#add').on('click', function () {
$.post('#Url.Action("Add")', $('form').serializeModel(), function(html) {
$('#container').html(html);
});
});
});
$.fn.serializeModel = function () {
var model = {};
$(this).serializeArray().map(function (item) {
model[item.name] = item.value;
});
return model;
};
</script>
Items.cshtml:
#model SampleModel
<table>
#for (var i = 0; i < Model.Items.Count; i++)
{
<tr>
<td>
#Html.HiddenFor(m => Model.Items[i].Id)
#Html.TextBoxFor(m => Model.Items[i].Name)
</td>
</tr>
}
</table>