I have following html form
<input type="hidden" name="JsonCustomers" data-bind="value: ko.toJSON(customers)" />
<input type="hidden" name="JsonMaterials" data-bind="value: ko.toJSON(materials)" />
<button type="submit" class="btn btn-sm btn-primary">Submit</button>
and input model class
public class SubmitViewModel
{
public string JsonCustomers { get; set; }
public string JsonMaterials { get; set; }
}
Controller action
[HttpPost]
public IActionResult Submit(SubmitViewModel model)
{
throw new NotImplementedException();
}
it is possible to automap Json into something like this ?
public class SubmitViewModel
{
public IEnumerable<InputCustomer> Customers { get; set; }
public IEnumerable<InputMaterial> Materials { get; set; }
}
I would like to skip conversion step from the Json into collection and ideally use data annotations with ModelState.IsValid function. Any idea ?
UPDATE
html
<input type="hidden" name="JsonCustomers" data-bind="value: ko.toJSON(customers)" />
<input type="hidden" name="JsonMaterials" data-bind="value: ko.toJSON(materials)" />
<input type="hidden" name="Customers" data-bind="value: ko.toJSON(customers)" />
<input type="hidden" name="Materials" data-bind="value: ko.toJSON(materials)" />
content of JsonCustomers after form submit
[{"isChecked":true,"name":"CompanyA","volume":"80","expectedDateOfOrder":"1.1.2018"},{"isChecked":true,"name":"CompanyB","volume":"100","expectedDateOfOrder":"2.2.2018"},{"isChecked":true,"name":"CompanyC","volume":"150","expectedDateOfOrder":"3.3.2018"}]
customer class
public class Customer
{
public bool? IsChecked { get; set; }
public string Name { get; set; }
public string Volume { get; set; }
public string ExpectedDateOfOrder { get; set; }
}
the issue is that public IEnumerable<Customer> Customers collection has Count = 0, i dont know why.
this is from FormCollection
With help from #Alex Riabov and based on this discussion https://github.com/aspnet/Mvc/issues/5760
model.Customers = JsonConvert.DeserializeObject<IEnumerable<InputCustomer>>(model.JsonCustomers);
in the controller action did the trick.
Related
I have a view model set up as:
public class BookViewModel
{
public int Id { get; set; }
[Display(Name = "Book Name")]
public string Name { get; set; }
public string Path { get; set; }
[Display(Name = "Pdf Book")]
public IFormFile BookPdfFile { get; set; }
}
In the view, I am using the view model properties
<form asp-action="CreateEdit" method="post" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input asp-for="Id" type="hidden" />
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
#if (!string.IsNullOrEmpty(Model.BookPath))
{
<div class="form-group">
<label>Uploaded Book:</label>
<a asp-action="Download" asp-route-id="#Model.Id">#Model.Name</a>
</div>
}
<div class="form-group">
<label asp-for="BookPdfFile" class="control-label"></label>
<input class="form-control" type="file" asp-for="BookPdfFile" accept=".pdf">
<span asp-validation-for="BookPdfFile" class="text-danger"></span>
</div>
</form>
Using this View model in the view in this way always asks for the file to be uploaded. If the file is uploaded during the create mode or if it has the file path, how to not ask to make the form file required?
The Book entity is:
public class Book
{
public int Id { get; set; }
[MaxLength(100)]
public string Title { get; set; }
[MaxLength(50)]
public string? Path { get; set; }
public ICollection<BookChapter> Chapters { get; set; }
}
How could I modify my code to make the form file not required if the path already exists in the DB?
On the server side I could add the validation attribute but how would I make that work on the client side as well?
I have yet not worked on posting and saving the files to the controller on HttpPost. So, my controller is just a one-line initializing the view model.
public async Task<IActionResult> CreateBook()
{
BookViewModel bookVM = new BookViewModel();
return View("CreateEdit", bookVM);
}
I've been working on my version of the app made in this tutorial (https://learn.microsoft.com/pl-pl/aspnet/core/data/ef-rp/complex-data-model?view=aspnetcore-5.0&tabs=visual-studio). And I've got question about a connection between two objects. My idea is to add more than one Material to Paczka. I've manage to connect them together but I can add only one Material. So my question is what should I do to be able to connect more than one?
Object Material
public class Material
{
[Key]
public int PaczkaID { get; set; }
public string Name { get; set; }
public string PN { get; set; }
public string Cert { get; set; }
public string Qty { get; set; }
public Paczka Paczka { get; set; }
}
And object Paczka
public class Paczka
{
public int PaczkaID { get; set; }
public string CRS { get; set; }
public string WO { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public Material Material { get; set; }
}
Here is how I can add Material to Paczka
public class MaterialModel : PageModel
{
private readonly Pasto.Data.PastoContext _context;
public MaterialModel(Pasto.Data.PastoContext context)
{
_context = context;
}
[BindProperty]
public Paczka Paczka { get; set; }
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Paczka = await _context.Paczkas
.Include(i => i.Material)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.PaczkaID == id);
if (Paczka == null)
{
return NotFound();
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int? id)
{
if (id == null)
{
return NotFound();
}
var paczkaToUpdate = await _context.Paczkas
.Include(i => i.Material)
.FirstOrDefaultAsync(s => s.PaczkaID == id);
if (paczkaToUpdate == null)
{
return NotFound();
}
if (await TryUpdateModelAsync<Paczka>(
paczkaToUpdate,
"Paczka",
i => i.Material))
{
if (String.IsNullOrWhiteSpace(
paczkaToUpdate.Material?.Name))
{
paczkaToUpdate.Material = null;
}
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return Page();
}
}
And HTML
a<form method="post">
<table class="table">
<thead>
<tr>
<th>
<strong>Name</strong>
</th>
<th>
<strong>P/N</strong>
</th>
<th>
<strong>Certificate</strong>
</th>
<th>
<strong>Quantity</strong>
</th>
</tr>
</thead>
<div class="form-group">
<tbody>
<tr>
<td>
<input asp-for="Paczka.Material.Name" class="form-control" />
</td>
<td>
<input asp-for="Paczka.Material.PN" class="form-control" />
</td>
<td>
<input asp-for="Paczka.Material.Cert" class="form-control" />
</td>
<td>
<input asp-for="Paczka.Material.Qty" class="form-control" />
</td>
</tr>
</tbody>
</div>
</table>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
<a asp-page="./Index" class="btn btn-danger">Back to List</a>
</div>
I want Paczka to have many Materials. The material should belong only
to one Paczka because I want to create them while creating Paczka
From your description, the Paczka and Materials should be configured one-to-many relationship. In the Paczka class, use List or ICollection to define the navigation property (Material), code like this:
public class Paczka
{
public int PaczkaID { get; set; }
public string CRS { get; set; }
public string WO { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public List<Material> Material { get; set; }
}
public class Material
{
[Key]
public int PaczkaID { get; set; }
public string Name { get; set; }
public string PN { get; set; }
public string Cert { get; set; }
public string Qty { get; set; }
//the foreign key
public int PaczkaForeignKey { get; set; }
[ForeignKey("PaczkaForeignKey")]
public Paczka Paczka { get; set; }
}
Then, in the View page, you could use #for statement to loop through the Material and display the related data, code like this:
#page
#model RazorPageSample.Pages.MaterialModel
#{
}
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Paczka.PaczkaID" />
<div class="form-group">
<label asp-for="Paczka.CRS" class="control-label"></label>
<input asp-for="Paczka.CRS" class="form-control" />
<span asp-validation-for="Paczka.CRS" class="text-danger"></span>
</div>
#for (var i = 0; i < Model.Paczka.Material.Count; i++)
{
<div class="form-group">
<label asp-for="Paczka.Material[i].PaczkaID" class="control-label"></label>
<input asp-for="Paczka.Material[i].PaczkaID" class="form-control" />
<span asp-validation-for="Paczka.Material[i].PaczkaID" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Paczka.Material[i].Name" class="control-label"></label>
<input asp-for="Paczka.Material[i].Name" class="form-control" />
<span asp-validation-for="Paczka.Material[i].Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Paczka.Material[i].Cert" class="control-label"></label>
<input asp-for="Paczka.Material[i].Cert" class="form-control" />
<span asp-validation-for="Paczka.Material[i].Cert" class="text-danger"></span>
</div>
}
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
Then, in the Post method, get the update value and insert or update the data to database, screenshot like this:
More detail information about relationships and update related entity, refer the following articles:
Relationships
Configuring One To Many Relationships in Entity Framework Core
Update related data - ASP.NET MVC with EF Core
I try to bind an object like this in a Action
public class MonthDataViewModel
{
public int Year { get; set; }
public int Month { get; set; }
public IEnumerable<MoneyDataItemViewModel> MoneyCosts { get; set; }
}
public class MoneyDataItemViewModel
{
public string Title { get; set; }
public decimal Cost { get; set; }
}
Is that possible? How do i design the form?
I try a few times but the property MoneyCosts won't be bind , and this is the data i submited:
Year=2016
Moneh=8
MoneyCosts.Title=ABC
MoneyCosts.Cost=100
MoneyCosts.Title=DEF
MoneyCosts.Cost=200
I saw a modelbinder called ArrayModelBinder<T> , how do i use it?
If you use x-www-url-formencoded content type then try to change(if possible) your post data like below:
Year=2016&Month=8&MoneyCosts[0].Title=ABC&MoneyCosts[0].Cost=100&MoneyCosts[1].Title=DEF&MoneyCosts[1].Cost=200
How do i design the form?
<form asp-controller="Home" asp-action="AccountName" method="post">
<input type="text" name="Year" />
<input type="text" name="Month" />
#for(var i = 0; i < count; i++)
{
<input type="text" name="#("MoneyCosts["+ i + "].Title")" />
<input type="text" name="#("MoneyCosts["+ i + "].Cost")" />
}
<input type="submit" value="Submit" />
</form>
If you use json content type, your data should be something like this:
{"Year": "2016", "Month":"8", "MoneyCosts":[{"Title":,"ABC"}, ...]}
in the case of json request you should use FromBody in action method.
[HttpPost]
public IActionResult ActionName([FromBody]MonthDataViewModel model)
I am trying to implement both Create and List actions in a single View. I was being suggested to use ViewModel. I am getting the object reference error. Also some good example on how to achieve this will help.
My Model Class
public class Employee
{
public int ID { get; set; }
[Required(ErrorMessage="Please enter name")]
public string Name { get; set; }
}
My View Model Class
public class EmployeeVM
{
public Employee Employee { get; set; }
public List<Employee> Employees { get; set; }
}
My Controller
[HttpPost]
public ActionResult Create(EmployeeVM emp, string Name)
{
if (ModelState.IsValid) //my modelstate is valid even when the value is empty string; it then gives an Object reference not set error
{
emp.Employee.Name = Name;
repository.SaveRole(emp);
return RedirectToAction("Index");
}
else
{
return View(emp);
}
}
My View
#model ERP.Domain.Entity.EmployeeVM
<body>
<div class="jumbotron">
#using (Html.BeginForm("Create", "MyController", FormMethod.Post))
{
#Html.ValidationSummary(true)
<label>
Name</label>
<input id="txtName" type="text" name="Name" class="btn btn-default" />
#Html.ValidationMessageFor(model => model.Employee.Name)
<input type="submit" value="Save" class="btn btn-primary" />
}
</div>
Also I was suggested in StackOverflow to go with ViewModel approach if I want to use both Create and List in the same View? Is it the right approach. Some example may help.
I have added an extra "title" field in my POST form and created a "title" attribute in my model. After the video has been uploaded the server is directed to another controller. This is done by setting the redirect field in the policy file. Now how can I access the title field in the redirected controller?
The POST form :
<form action="#Model.FileUploadModel.FormAction" method="#Model.FileUploadModel.FormMethod" enctype="#Model.FileUploadModel.FormEnclosureType" >
<input type="hidden" name="key" value="#Model.FileUploadModel.FileId" />
<input type="hidden" name="AWSAccessKeyId" value="#Model.FileUploadModel.AWSAccessKey" />
<input type="hidden" name="acl" value="#Model.FileUploadModel.Acl" />
<input type="hidden" name="policy" value="#Model.FileUploadModel.Base64EncodedPolicy" />
<input type="hidden" name="signature" value="#Model.FileUploadModel.Signature" />
<input type="hidden" name="redirect" value="#Model.FileUploadModel.RedirectUrl" />
<div class="row">
**<label for="Title" style="padding-right: 5px;">Title (optional) </label>
<input type="text" name="Title" style="width: 200px;" />**
</div>
<div class="row_clear"></div>
<div class="row">
<input type="file" name="file" size="100" id="file"/>
</div>
</form>
and my FileUploadModel
public FileUploadModel(string publicKey, string privateKey, string bucketName, string redirectUrl)
{
myPrivateKey = privateKey;
FormAction = string.Format("https://{0}.s3.amazonaws.com/", bucketName);
FormMethod = "post";
FormEnclosureType = "multipart/form-data";
Bucket = bucketName;
FileId = "u5/i/" + Guid.NewGuid().ToString();
AWSAccessKey = publicKey;
Acl = "private";
RedirectUrl = redirectUrl;
}
public string FormAction { get; private set; }
public string FormMethod { get; private set; }
public string FormEnclosureType { get; private set; }
public string Bucket { get; private set; }
public string Acl { get; private set; }
public string Policy { get; private set; }
public string FileId { get; private set; }
public string AWSAccessKey { get; private set; }
public string RedirectUrl { get; private set; }
[Display(Name = "Title (optional)")]
public string Title { get; set; }
[Display(Name = "File")]
public HttpPostedFileBase File { get; set; }
public int? Page { get; set; }
public string SearchString { get; set; }
}
}
Here is the link I referred for creating policy form.
Link
I worked around the problem by sending the title attribute through the URL.
In my RouteConfig file I added a new route:
routes.MapRoute(
name: "Transcode",
url: "{controller}/{action}/{title}/{id}",
defaults: new
{
controller = "Videos",
action = "Transcode",
id = UrlParameter.Optional
}
);
and then in the Transcode action I added a title parameter. Then finally the value I passed to the redirect URL attribute was: " http://dev.area.local/Videos/Transcode/title"