IFormFile is not bound if nested in view model - razor

I am having problem with binding nested IFormFile in .net core mvc project.
If I put my IFormFile in nested view model it will not be bound to it on post.
For example this does not work:
public class SomeVM
{
public GalleryVM Gallery { get; set; }
}
public class GalleryVM
{
public IFormFile UploadingImage { get; set; }
//gallery properties...
}
View:
#model SomeVM
<form method="post" enctype="multipart/form-data">
<input type="file" name="Gallery.UploadingImage" />
<input type="submit" value="save" />
</form>
Some code was omitted for brevity.

I found the solution to that so I want to share it with you. I found that it is known issue and it should be solved in .net core 2.0 issue on github
Current hack is to send some extra data when uploading file.
public class SomeVM
{
public GalleryVM Gallery { get; set; }
}
public class GalleryVM
{
public IFormFile UploadingImage { get; set; }
public bool FormFileHack { get; set; }
//gallery properties...
}
//the view .cshtml
<input type="file" name="Gallery.UploadingImage" />
<input type="hidden" name="Gallery.FormFileHack" />

Related

How do you link to other razor pages and include a query string parameter in Blazor WASM?

I've got a Blazor WASM app. It has two razor pages:
Documentation.razor:
#page "/documentation"
ViewRecord.razor:
#page "/documentation/ViewRecord"
I have a DocumentationController too.
I want to create a few hyperlinks within the Documentation razor page that have hyperlinks in this format:
/Documentation/ViewRecord?recordtype=randomWord1
/Documentation/ViewRecord?recordtype=randomWord2
/Documentation/ViewRecord?recordtype=randomWord3
Is there a cleaner way to do this, similar to using ActionLinks, instead of having to do something like this:
link1
link2
link3
This is the beauty of Blazor and Razor Components, if you find you want to something, you can create a re-usable component for it yourself. Complete flexibility. Say for instance we have the following component:
UrlBuilder.razor
#if (!string.IsNullOrEmpty(FullUrl))
{
#LinkDesc
}
#code
{
[Parameter]
public string LinkDesc { get; set; }
[Parameter]
public string Controller { get; set; }
[Parameter]
public string Action { get; set; }
[Parameter]
public string UrlParameter { get; set; }
private string FullUrl { get; set; }
protected override void OnInitialized()
{
FullUrl = $"{Controller}/{Action}?{UrlParameter}";
}
}
You can then access that component anywhere through your application like so:
<UrlBuilder LinkDesc="link 1" Controller="Documentation" Action="ViewRecord" UrlParameter="#word3" />
Is that any easier than creating a a href manually? Absolutely not, however, you could customize it to your delight.

Blazor Razor Validation Message don't display from component library

I have created a library of blazor components to be able to call the components from the app but the message validation doesn't show. At the moment, the validation is done in a InputText (it validates the format or the length of the Input) but the message or the style of the component is not shown.
The code of the component library:
CustomInputText
<input value="#Value" #oninput="OnValueChanged" placeholder=#placeholderText class="form-control i-component o-my-4" />
<ValidationMessage For="#(() => model)" />
#code {
[Parameter]
public string placeholderText { get; set; }
[Parameter]
public object model { get; set; }
[Parameter]
public string Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
private Task OnValueChanged(ChangeEventArgs e)
{
Value = e.Value.ToString();
return ValueChanged.InvokeAsync(Value);
}
}
I import the component from a nuget package to be able to use it in the App
The App code:
<CustomInputText placeholderText="Place Holder Test" model="filterPayroll.IPF" #bind-Value="filterPayroll.IPF"/>
When I put the ValidationMessage directly in the app it works correctly, but not in the library. For the two cases, the validation linked to the "filterPayroll" class is done correctly, the difference is that in one the message is displayed and the other does not.
You need to pass the For for the Validation Summary as an expression.
CustomInputText needs to look like this:
<input value="#Value" #oninput="OnValueChanged" placeholder=#placeholderText class="form-control i-component o-my-4" />
<ValidationMessage For="#For" />
#code {
[Parameter]
public string placeholderText { get; set; }
[Parameter] public Expression<Func<string>>? For { get; set; }
[Parameter]
public string Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
private Task OnValueChanged(ChangeEventArgs e)
{
Value = e.Value.ToString();
return ValueChanged.InvokeAsync(Value);
}
}
And your markup:
<CustomInputText #bind-Value="filterPayRoll.IDF" For="() => filterPayRoll.IDF" />

MVC Radio Button List Grid

I've got an issue trying to read the selected values from the radio button list in my [HttpPost] Controller. Any help would be greatly appreciated.
My models are the following:
public partial class RoleModel
{
public Guid RoleId { get; set; }
public string Description { get; set; }
public List<RoleModuleAccessRight> RoleModuleAccessRights { get; set; }
}
public class RoleModuleAccessRight
{
public string ModuleName { get; set; }
public int ModuleId { get; set; }
public bool HasFullControl { get; set; }
public bool HasReadOnly { get; set; }
public bool HasNoAccess { get; set; }
}
My Controllers:
[HttpGet]
public ActionResult Edit(Guid id)
{
RoleModel role = BusinessLayer.UserManager.GetRoleModel(id);
role.RoleModuleAccessRights = BusinessLayer.UserManager.GetModulesForRoleId(role.RoleId);
return PartialView("_Edit", role);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(RoleModel model)
{
if (ModelState.IsValid)
{
BusinessLayer.UserManager.UpdateAccessRights(model);
string url = Url.Action("List", "Roles");
return Json(new { success = true, url = url });
}
return PartialView("_Edit", model);
}
My View code:
#foreach (RoleModuleAccessRight item in Model.RoleModuleAccessRights)
{
#Html.HiddenFor(model => item.ModuleId)
string radioName = string.Format("RoleModuleAccessRights_{0}", item.ModuleId.ToString());
<tr>
<td>#item.ModuleName</td>
<td>
<div class="checkbox">
#Html.RadioButton(radioName, item.HasFullControl, item.HasFullControl)
</div>
</td>
<td>
<div class="checkbox">
#Html.RadioButton(radioName, item.HasReadOnly, item.HasReadOnly)
</div>
</td>
<td>
<div class="checkbox">
#Html.RadioButton(radioName, item.HasNoAccess, item.HasNoAccess)
</div>
</td>
</tr>
}
The issue im having is that when i post the form im not able to grab the information in my [HttpPost] Controller. It returns "null"
The mark up generated is the following:
Looking at your code, your ids are not unique which is going to break things, also using a dedicated template will simplify your problem. See this example https://stackoverflow.com/a/7668325
Another more generic article: Multiple radio button groups in MVC 4 Razor

Passing a lot of parameters to a controller?

I am creating an "advanced input form" with a lot of inputs for searching data. My question is: What is the best way to pass a lot of data from HTML to the controller.
The reason i ask is. Lets say you have this HTML form:
#using (Html.BeginForm("Loading", "AdvancedSearch"))
{
<input type="text" id="keyword">
<input type="text" id="keyword1">
<input type="text" id="keyword2">
<input type="text" id="keyword3">
<input type="text" id="keyword4">
<input type="text" id="keyword5">
<input type="text" id="keyword6">
<input type="text" id="keyword7">
<input type="text" id="keyword8">
<input type="text" id="keyword9">
<input type="submit" value="Search" style="width: 150px;" />
}
Then it will be pretty nasty to pass it all to the controller like this (I've got a lot more keywords):
public ActionResult Loading(string keyword1, string keyword2, string keyword3, string keyword4, string keyword5, string6
string keyword7, string keyword8, string keyword9){
//do things to the parameters!
return View();
}
So how would you perform this action or would you do it like this?
Thanks!
Use a model class. Provided the input names match the model properties, the MVC engine will do the mapping for you.
public class Keywords
{
public string keyword1 { get; set; }
public string keyword2 { get; set; }
///etc...
}
And you action is much simpler:
public ActionResult Loading(Keywords keywords){
//do things to the parameters!
var keyword1 = keywords.keyword1;
return View();
}
I would suggest to use a view model and include a list for your keywords. It is non-sense to add a property for every keyword:
public class Keywords
{
public List<string> Items { get; set; }
}
public ActionResult Loading(Keywords keywords){ }
Or if possible:
public ActionResult Loading(List<string> keywords){ }
Read some more about it here.
Create a class with those keyword1, keyword2 and so on...
Such as
public class SearchDto
{
public string Keyword1 { get; set; }
public string Keyword2 { get; set; }
public string Keyword3 { get; set; }
public string Keyword4 { get; set; }
public string Keyword5 { get; set; }
public string Keyword6 { get; set; }
public string Keyword7 { get; set; }
public string Keyword8 { get; set; }
}
Then ActionResult such as
public ActionResult Loading(SearchDto dto)
{
return View();
}
You can post your data from view.
There is an example Send JSON data via POST (ajax) and receive json response from Controller (MVC) here.
And also here
function search() {
$.ajax({
type: "POST",
url: '#Url.Action("CreateEmail", "Email")',
data: JSON.stringify({
Keyword1 : $("#keyword1").val(),
Keyword2 : $("#keyword2").val(),
Keyword3 : $("#keyword3").val(),
Keyword4 : $("#keyword4").val(),
Keyword5 : $("#keyword5").val(),
Keyword6 : $("#keyword6").val(),
Keyword7 : $("#keyword7").val(),
Keyword8 : $("#keyword8").val(),
}),
contentType: "application/json; charset=utf-8",
async: false,
dataType: "json",
success: function (result){
alert('done');
}
)};

DataAnnotatons with EF and DataBaseFirst not working

I've modified the code in my model (which is on a separate tier) with metadata hoping this will resolve the issue, but it didn't. It's as if the DataAnnotations from the model are not being picked up? Why???
When viewing the page source, I should be seeing something like the following:
data-val-required="The User name field is required."
However, all I see is the fiollowing:
<div class="editor-label">
<label for="Email">Email</label>
</div>
<div class="editor-field">
<input id="Email" name="Email" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="Email" data-valmsg-replace="true"></span>
</div>
When looking at the model for standard account log in, I have the following:
YeagerTech.Models.LogOnModel
The above is defined in the following Models folder on the root of the website inside the AccountModels.cs file:
public class LogOnModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
The above (while the model is on the website front end and not in another tier) works fine and has the required attributes set up in the page source fine.What do I need to do to in order to perform data validation with the EF DataBaseFirst method where my model resides on another tier?
My web.config file is set up as follows:
<appSettings>
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
On the front-end, I have my view set up as follows:
#model YeagerTech.YeagerTechWcfService.Customer
#{
ViewBag.Title = "Create Customer";
}
<h2>Create</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Ajax.BeginForm("Create", "Customer", new AjaxOptions { HttpMethod = "POST"}))
{
#Html.ValidationSummary(true, "Create was unsuccessful. Please correct the errors and try again.")
<fieldset>
<legend>Customer</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Email)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Email)
#Html.ValidationMessageFor(model => model.Email)
</div>
The definition for YeagerTech.YeagerTechWcfService.Customer is set up as follows (inside the Reference.cs file)
[System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")] [System.Runtime.Serialization.DataContractAttribute(Name="Customer", Namespace="http://schemas.datacontract.org/2004/07/YeagerTechModel")] [System.SerializableAttribute()] public partial class Customer : YeagerTech.YeagerTechWcfService.CustomerDA { }
My model is setup as follows inside the above service:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ServiceModel;
using System.Runtime.Serialization;
namespace YeagerTechModel
{
[Serializable]
[DataContract]
[MetadataType(typeof(CustomerDA))]
public partial class Customer : CustomerDA
{
public Customer()
{
this.Projects = new HashSet<Project>();
}
}
[Serializable]
[DataContract]
public class CustomerDA
{
[DataMember]
public short CustomerID { get; set; }
[Required]
[StringLength(50)]
[DataType(DataType.EmailAddress)]
[DataMember]
public string Email { get; set; }
Base DbContext class
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
namespace YeagerTechModel
{
public partial class YeagerTechEntities : DbContext
{
public YeagerTechEntities()
: base("name=YeagerTechEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Customer> Customers { get; set; }
}
}
I see from your code that you included the jquery.validate.js script and the jquery.validate.unobtrusive.js script. However, I do not see that the jquery script was included. You must include jquery, and it must be the first script. In addition, since you are using MVC's Ajax capabilities, you also need to include the jquery.unobtrusive-ajax.js script as well.