As a part of my first project i had to implement image and video display in views using MVC 5. It is working and i'm quite happy with the achievement, however there are some improvements had to be made, how would you recommend to improve following code:
to display a picture i used this code:
<div style="float: none; margin: auto auto 30px auto ;height: 280px; width: 400px; ">
<div id="imageContainer">
<img src="~/Images/#Html.Raw(Model.ImagePath)" />
</div>
</div>
in a case when i don't provide a path, this container stays empty but still visible as empty block on the page with image icon, indicating that image suppose to be here... How i can get rid of it, if i dont want picture to be shown in this view?
The same situation with video container, i simply using embeded youtube link as a Videolink for video:
<iframe src=#Html.Raw(Model.VideoLink) width="560" height="315" frameborder="1" align="middle" display:block style="margin:auto auto 30px auto"></iframe>
If i provide Videolink - it works fine, if i dont, i have a container showing an error. Is there a way to simply hide those containers if there are no input made?
i'll show you the way i set it all up, it all in one class Media
public class Media
{
public int Id { get; set; }
public string title { get; set; }
[Required, StringLength(512)]
public string description { get; set; }
[Required]
[AllowHtml]
public string body { get; set; }
public string ImagePath { get; set; }
public string VideoLink { get; set; }
public string Source { get; set; }
public TagsEnum TagsEnum { get; set; }
[Column(TypeName = "datetime2")]
[DisplayFormat(DataFormatString = "{0:dd MMMM , yyyy HH:mm}", ApplyFormatInEditMode = true)]
public DateTime? NewsDate { get; set; }
}
Update
this is what i think it should look like using Sam's advise, right? If so, it is still not giving right result
#if (!string.IsNullOrEmpty(b.ImagePath))
{
}
else
{
<div style="float: right; margin: 10px 10px 25px 25px; height: 100px; width:150px">
<div id="imageContainer">
<img src="~/Images/#Html.Raw(b.ImagePath))" style='height: 100%; width: 100%;' />
</div>
</div>
}
#if (!string.IsNullOrEmpty(b.ImagePath)) {
#<div style="float: right; margin: 10px 10px 25px 25px; height: 100px; width:150px">
<div id="imageContainer">
<img src="~/Images/#Html.Raw(b.ImagePath))" style='height: 100%; width: 100%;' />
</div>
</div>
}
Related
I need to implement this kind of view using mudblazor image components:
I got the images correct, but how can I add text on top of the image like in the picture above? Here is my code:
<MudGrid>
u/foreach (var item in tabscatitm)
{
<MudItem xs="3" >
<MudImage Src=#item.ItemImageUrl Height="120" Width="120" u/onclick="ItemClick"
Class="rounded-lg"Style="border:thin;border-color:darkgray;border-radius:10px"/>
<MudText Typo="Typo.h6">#item.ItemName</MudText>
<MudText Typo="Typo.body2">#item.ItemShortName</MudText>
</MudItem>
}
</MudGrid>
You could try something like this:
CustomImage.razor
<div style="position: relative; cursor: pointer" #onclick="#this.Clicked">
#* the image *#
<MudImage Src="#this.Src" Alt="#this.Alt"/>
#* A transparent background for the text *#
<MudPaper
Elevation="0"
Class="rounded-0"
Style="position: absolute; bottom: 0; left: 0; padding: 25px; width: 100vw; opacity: 0.6">
#* Hack: the text is needed to give the transparent part the proper height, but will also display the text in 0.6 transparency *#
<MudText Typo="Typo.h4">#this.Name</MudText>
<MudText Typo="Typo.h6">#this.ShortName</MudText>
</MudPaper>
#* The text (in black) *#
<span style="position: absolute; bottom: 0; left: 0; padding: 25px; width: 100vw; color: black">
<MudText Typo="Typo.h4">#this.Name</MudText>
<MudText Typo="Typo.h6">#this.ShortName</MudText>
</span>
</div>
#code {
[Parameter]
public string Src { get; set; } = default!;
[Parameter]
public string Alt { get; set; } = string.Empty;
[Parameter]
public string Name { get; set; } = default!;
[Parameter]
public string ShortName { get; set; } = default!;
[Parameter]
public EventCallback OnClick { get; set; }
private async Task Clicked()
{
await this.OnClick.InvokeAsync();
}
}
If used like this:
<MudGrid>
#foreach (var item in this.items)
{
<MudItem md="6">
<CustomImage
Src="#item.Src"
Name="#item.Name"
ShortName="#item.ShortName"
OnClick="#(() => this.Click(item))"/>
</MudItem>
}
</MudGrid>
#code {
private readonly List<Item> items = new()
{
new Item { Name = "A tasty burger", ShortName = "Some more text or a price...", Src = "images/hamburger.jpg" },
new Item { Name = "Another burger", ShortName = "...", Src = "images/hamburger.jpg" }
};
private void Click(Item item)
{
Console.WriteLine($"Clicked {item.Name}");
}
}
The result will be:
Note the comment about the hack: my CSS knowledge is rather limited, but I'm sure some colleague of yours can help with that. This is just a quick demo for you to get started.
I have created a view that accepts a ProjectsCreatorsVM class. This class has been structured this way:
public class ProjectsCreatorsVM
{
public List<ProjectVM> ProjectsCreators { get; set; }
public ProjectsCreatorsVM(List<ProjectVM> projectsCreators)
{
ProjectsCreators = projectsCreators;
}
}
In addition, the ProjectVM follow this structure:
public class ProjectVM
{
public Project Project { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public ProjectVM(Project pro, ApplicationUser applUser)
{
Project = pro;
ApplicationUser = applUser;
}
}
Lastly, my view tries to go through the ProjectsCreators.Project but it does not seem to be possible.
<div class="card-content-container" >
#foreach (Project obj in #Model.ProjectsCreators.)
{
<div class="card">
<img class="card-img-top" src="#obj.ImgURL" alt ="project image" >
<div class="card-body d-flex flex-column">
<h5 class="card-title">#obj.Title</h5>
<h6 class="card-title">#obj.Title</h6>
<p class="card-text">
#obj.TruncatedDescription
</p>
<div class="mt-auto" style="display: flex; justify-content: space-between; align-items: center;">
View Details
</div>
</div>
</div>
I would appreciate any help. Thanks in advance.
ProjectCreators is a List and when you iterate ProjectCreators you get a ProjectVM object not a Project or ApplicaionUser instance. If you want to access Project instance add Project after #obj like #obj.Project.Title
<div class="card-content-container" >
#foreach (ProjectVM obj in #Model.ProjectsCreators.)
{
<div class="card">
<img class="card-img-top" src="#obj.Project.ImgURL" alt ="project image" >
<div class="card-body d-flex flex-column">
<h5 class="card-title">#obj.Project.Title</h5>
<h6 class="card-title">#obj.Project.Title</h6>
<p class="card-text">
#obj.Project.TruncatedDescription
</p>
<div class="mt-auto" style="display: flex; justify-content: space-between; align-items: center;">
View Details
</div>
</div>
</div>
}
</div>
To achieve what I wanted, I created another class. That looks like this:
public class ProjectAndUserVM
{
public string ProjectTitle { get; set; }
public string ProjectId { get; set; }
public string ProjectImageUrl { get; set; }
public string ProjectDescription { get; set; }
public string ProjectCreatorName { get; set; }
public string ProjectCreatorId { get; set; }
public string ProjectCreatorEmail { get; set; }
public ProjectAndUserVM(string projectTitle, string projectId, string projectImageUrl, string projectDescription, string projectCreatorName, string projectCreatorId, string projectCreatorEmail)
{
ProjectTitle = projectTitle;
ProjectId = projectId;
ProjectImageUrl = projectImageUrl;
ProjectDescription = projectDescription;
ProjectCreatorName = projectCreatorName;
ProjectCreatorId = projectCreatorId;
ProjectCreatorEmail = projectCreatorEmail;
}
}
So, basically my controller is returning that as a list which I convert to an IEnumerable. and I use that list on my view instead.
In the following Blazor (server-side) code snips. How to prompt the confirmation dialog?
<tbody>
#foreach (var r in lists)
{
var s = r.ID;
<tr>
<td>#s</td>
<td><button class="btn btn-primary" #onclick="() => DeleteSymbol(s)">Delete</button></td>
</tr>
}
</tbody>
#code {
async Task DeleteSymbol(string id)
{
// Get confirmation response from user before running deletion?
// Delete!
}
}
#inject IJSRuntime JsRuntime
<tbody>
...
</tbody>
#code {
async Task DeleteSymbol(string id)
{
bool confirmed = await JsRuntime.InvokeAsync<bool>("confirm", "Are you sure?");
if (confirmed)
{
// Delete!
}
}
}
I created a simple popconfirm component.
<div class="pop-container">
#if (Show)
{
<div class="popconfirm">
#Message
<button type="button" class="btn btn-warning" #onclick="() => Confirmation(false)">No</button>
<button type="button" class="btn btn-primary" #onclick="() => Confirmation(true)">Yes</button>
</div>
}
<button type="button" class="#Class" #onclick="ShowPop">#Title</button>
</div>
#code {
public bool Show { get; set; }
[Parameter] public string Title { get; set; } = "Delete";
[Parameter] public string Class { get; set; } = "btn btn-danger";
[Parameter] public string Message { get; set; } = "Are you sure?";
[Parameter] public EventCallback<bool> ConfirmedChanged { get; set; }
public async Task Confirmation(bool value)
{
Show = false;
await ConfirmedChanged.InvokeAsync(value);
}
public void ShowPop()
{
Show = true;
}
}
CSS
.pop-container{
position: relative;
}
.popconfirm{
background-color: white;
border-style: solid;
border-width: 1px;
border-color: lightblue;
width: 250px;
position: absolute;
top: -50px;
padding: 10px;
border-radius: 8px;
}
Usage
<Popconfirm ConfirmedChanged="Test" />
#code{
public void Test(bool test)
{
Console.WriteLine(test);
}
}
I used #Egill's answer and extended it a bit more to fit my requirement.
This is what I needed.
The delete button as an image. (using ion-icons)
The background disabled
Popup in center of the screen.
Using RenderFragments for the particular requirements.
This is what I ended up with.
Component:
<div>
<button type="button" class="#Class" #onclick="ShowPop">
#if (ButtonContent != null)
{
#ButtonContent
}
else
{
#Title
}
</button>
#if (Show)
{
<div class="pop-container">
<div class="popconfirm gradient-to-gray">
<div class="row">
<div class="col">
#if (MessageTemplate != null)
{
#MessageTemplate
}
else
{
#Message
}
</div>
</div>
<div class="row">
<div class="col-6">
<button type="button" class="btn #YesCssClass" #onclick="() => Confirmation(true)">Yes</button>
</div>
<div class="col-6">
<button type="button" class="btn #NoCssClass" #onclick="() => Confirmation(false)">No</button>
</div>
</div>
</div>
</div>
}
</div>
#code {
public class MessageTemp
{
public string msg { get; set; }
}
public bool Show { get; set; }
[Parameter]
public string Title { get; set; } = "Delete";
[Parameter]
public string Class { get; set; } = "btn btn-danger";
[Parameter]
public string YesCssClass { get; set; } = "btn btn-success";
[Parameter]
public string NoCssClass { get; set; } = "btn btn-warning";
[Parameter]
public string Message { get; set; } = "Are you sure?";
[Parameter]
public EventCallback<bool> ConfirmedChanged { get; set; }
[Parameter]
public RenderFragment ButtonContent { get; set; }
[Parameter]
public RenderFragment MessageTemplate { get; set; }
public async Task Confirmation(bool value)
{
Show = false;
await ConfirmedChanged.InvokeAsync(value);
}
public void ShowPop()
{
Show = true;
}
}
CSS:
.pop-container {
z-index: 1000;
width: 100vw;
height: 100vh;
position: fixed;
top: 0px;
left: 0px;
background-color: rgba(132, 4, 4, 0.77);
}
.popconfirm {
color: white;
background-color: gray;
border-style: solid;
border-width: 1px;
border-color: lightblue;
padding: 10px;
border-radius: 15px;
min-width: 250px;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.pop-message-card, .pop-message-card div {
background-color: rgba(0, 0, 0, 0.00)
}
.gradient-to-gray {
background: linear-gradient(0deg,#000000,#888);
color: #fff;
}
.btn-yes {
background-color: darkgreen;
color: limegreen;
width: 100%;
}
.btn-no {
background-color: darkred;
color: lightcoral;
width: 100%;
}
Usage:
<ConfirmPopup Title="Delete Unit"
Message="Delete record?"
Class="btn btn-psl-delete"
YesCssClass="btn-yes"
NoCssClass="btn-no"
ConfirmedChanged="DeleteRecord">
<ButtonContent>
<ion-icon name="trash-outline" style="font-size:2em;color:white;"></ion-icon>
</ButtonContent>
<MessageTemplate>
<div class="card pop-message-card">
<div class="card-header">
Delete Record?
</div>
<div class="card-body">
<p>
This will delete the record!<br />
Are you sure?
</p>
</div>
</div>
</MessageTemplate>
</ConfirmPopup>
<button title="Delete" type="button" #onclick="#(async () => await delete(id))" class="m-0 btn btn-outline-secondary">
async Task delete(string id)
{
bool confirmed = await js.InvokeAsync<bool>
("confirm", "Delete?");
if (confirmed)
{
// delete
}
}
Hey Guys I need help on this, I know I can return List<string> in HTML.BeginForm which looks like this:
#using (Html.BeginForm("Test", "Home", FormMethod.Post, new {#class = "form-horizontal", role = "form"}))
{
<textarea name="logic" style="width: 10em; height: 10em;"></textarea>
<textarea name="logic" style="width: 10em; height: 10em;"></textarea>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Register" />
</div>
</div>
}
I can return List<string> Logic on my Controller HTTPOST
But my question is how will I do it if I need to return a List of Object? Not using a Model because I need to return 2 Objects so I am was thinking like my controller should get it something like this
public ActionResult Test(List<Model1> model1, List<Model2> model2)
Thanks for those who can help
You can do in provided way
First declare class
public class TestModel
{
public string Name { get; set; }
public string Text { get; set; }
}
Second Controller to accept list of model
[HttpPost]
public ActionResult Test(List<TestModel> model1, List<TestModel> model2)
{
return View();
}
Last, View to pass model values
#using (Html.BeginForm("Test", "Home", FormMethod.Post, new {#class = "form-horizontal", role = "form"}))
{
<textarea name="model1[0].Name" style="width: 10em; height: 10em;"></textarea>
<textarea name="model1[0].Text" style="width: 10em; height: 10em;"></textarea>
<textarea name="model1[1].Name" style="width: 10em; height: 10em;"></textarea>
<textarea name="model1[1].Text" style="width: 10em; height: 10em;"></textarea>
<textarea name="model2[0].Name" style="width: 10em; height: 10em;"></textarea>
<textarea name="model2[0].Text" style="width: 10em; height: 10em;"></textarea>
<textarea name="model2[1].Name" style="width: 10em; height: 10em;"></textarea>
<textarea name="model2[1].Text" style="width: 10em; height: 10em;"></textarea>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Register" />
</div>
</div>
}
Put the breakpoint inside Test controller you will see list populated
I would suggest to wrap these lists into a view model class like this -
public class ModelView
{
public List<Model1> model1 { get; set; }
public List<Model2> model2 { get; set; }
}
...and then the controller will look like -
public ActionResult Test(ModelView modelView)
Within the controller you can easily access the lists.
modelView.model1
modelView.model2
If you want to generate a view with multiple model objects then you need to create a ViewModel comprising properties that are needed from those models. And then reference the view with this ViewModel.
public class Model1
{
public string prop11 { get; set; }
public string prop12 { get; set; }
}
public class Model2
{
public string prop21 { get; set; }
public string prop22 { get; set; }
}
public class ViewModel
{
public List<Model1> model1 { get; set; }
public List<Model2> model2 { get; set; }
}
Then generate the view referencing the viewmodel that will get the properties from both models.
controller action that will be hit from that view:
public ActionResult Test(ModelView modelView) // you can access the viewmodel properties
On my Index view, I need to allow the user to enter search criteria. This criteria is sent back to the Index controller in the form of a FormCollection object. I then extract the search criteria, pull the requested information from the database, and send the user back to the Index view. However, once the user get's back to the index view with the requested info, the data from the FormCollection object is now blank.
I would like to be able to keep the user's search criteria in the three text boxes that I use, however I'm not sure how using a FormCollection. Does anyone know either how to do this or another I should use?
View
#using(Html.BeginForm())
{
<div id="searchBox" class="boxMe">
<div id="zipBox" style="float: left; padding-left: 20px; padding-right: 10px; padding-top: 20px; padding-bottom: 20px; vertical-align: top;">
#Html.Raw("Zip Code")
#Html.TextArea("ZipSearch", new { style = "width: 300px;", placeholder = "Enter up to 35 comma separated zip codes" })
</div>
<div id="dateBox" style="float: left; padding-right: 10px; padding-top: 20px;">
#Html.Raw("Effective on this date")
#Html.TextBox("DateSearch", null, new { style="width: 80px;"})
</div>
<div id="stateBox" style="float: left; padding-right: 20px; padding-top: 20px;">
#Html.Raw("State")
#Html.TextBox("StateSearch", null, new { style = "width: 25px;" })
<button type="submit">Search</button>
</div>
</div>
<div style="clear: both;"></div>
}
Controller
public ViewResult Index(FormCollection searchDetails = null)
{
string zip = searchDetails["ZipSearch"];
string date = searchDetails["DateSearch"];
string state = searchDetails["StateSearch"];
if (String.IsNullOrWhiteSpace(zip) && String.IsNullOrWhiteSpace(date) && String.IsNullOrWhiteSpace(state))
{
return View();
}
string[] zipArray;
DateTime effectiveDate;
//Convert date string to DateTime type
if (String.IsNullOrWhiteSpace(date))
{
effectiveDate = DateTime.MinValue;
}
else
{
effectiveDate = Convert.ToDateTime(date);
}
//Conduct search based on Zip Codes
if (!String.IsNullOrWhiteSpace(zip))
{
//Create array and remove white spaces
zipArray = zip.Split(',').Distinct().ToArray();
for (int i = 0; i < zipArray.Length; i++)
{
zipArray[i] = zipArray[i].Trim();
}
//Add zip codes to list object then send back to view
List<ZipCodeTerritory> zips = new List<ZipCodeTerritory>();
if (String.IsNullOrWhiteSpace(state))
{
foreach (var items in zipArray)
{
var item = from z in db.ZipCodeTerritory
where z.ZipCode.Equals(items) &&
z.EffectiveDate >= effectiveDate
select z;
zips.AddRange(item);
}
}
else
{
foreach (var items in zipArray)
{
var item = from z in db.ZipCodeTerritory
where z.ZipCode.Equals(items) &&
z.EffectiveDate >= effectiveDate
select z;
zips.AddRange(item);
}
}
return View(zips);
}
//Zip code was not specified so search by state
if (!String.IsNullOrWhiteSpace(state))
{
var items = from z in db.ZipCodeTerritory
where z.StateCode.Equals(state) &&
z.EffectiveDate >= effectiveDate
select z;
return View(items);
}
//Neither zip code or state specified, simply search by date
var dateOnly = from z in db.ZipCodeTerritory
where z.EffectiveDate >= effectiveDate
select z;
return View(dateOnly);
}
EDIT
Following the instructions below I've created my View model like so:
public class ZipCodeIndex
{
public List<ZipCodeTerritory> zipCodeTerritory { get; set; }
public string searchZip { get; set; }
public string searchDate { get; set; }
public string searchState { get; set; }
}
However in my View I cannot access any of these properties. The header for the view is written like this:
#model IEnumerable<Monet.ViewModel.ZipCodeIndex>
However all the TextBoxFor and the TextAreaFor helpers say none of the specified properties exist.
#using(Html.BeginForm("Index", "ZipCodeTerritory", FormMethod.Post))
{
<div id="searchBox" class="boxMe">
<div id="zipBox" style="float: left; padding-left: 20px; padding-right: 10px; padding-top: 20px; padding-bottom: 20px; vertical-align: top;">
#Html.Raw("Zip Code")
#Html.TextAreaFor(model => model.searchZip, new { style = "width: 300px;", placeholder = "Enter up to 35 comma separated zip codes" })
</div>
<div id="dateBox" style="float: left; padding-right: 10px; padding-top: 20px;">
#Html.Raw("Effective on this date")
#Html.TextBoxFor(model => model.searchDate, null, new { style="width: 80px;"})
</div>
<div id="stateBox" style="float: left; padding-right: 20px; padding-top: 20px;">
#Html.Raw("State")
#Html.TextBoxFor(model => model.searchState, null, new { style = "width: 25px;" })
<button type="submit">Search</button>
</div>
</div>
<div style="clear: both;"></div>
}
FINAL EDIT
Missed that the page was looking for an IEnuerable Model object. Changed the header to this and fixed the problem.
#model Monet.ViewModel.ZipCodeIndex
Ok so the first thing you want to do is create a model just a new class file that looks something like this.
public class AddressModel
{
public int zip{ get; set; }
public string state{ get; set; }
public string date{ get; set; }
}
Then another new class file called a view model something like this. This way you can reference it for different things. Have like your search address then return the results in seperate list.
public class AddressViewModel
{
public AddressModel SearchAddress { get; set; }
public List<AddressModel> ResultsAddress{ get; set; }
}
Then in your view you reference the viewmodel like this #model MVCTestProject.ViewModels.InsertPage
on the top line.
Then these will be your text boxes.
#using (Html.BeginForm("Submit", "Insert", FormMethod.Post))
{
#Html.TextBoxFor(model => model.SearchAddress.zip)
#Html.TextBoxFor(model => model.SearchAddress.state)
#Html.TextBoxFor(model => model.SearchAddress.date)
<input id="button" name="button" type="submit" value="submit" />
}
And in your controller it will be similar to this.
public ActionResult Submit(AddressViewModel model)
{
model.ResultsAddress = (from z in db.ZipCodeTerritory
where z.StateCode.Equals(state) &&
z.EffectiveDate >= model.SearchAddress.date
select new AddressModel {
date = z.effectiveDate }).toList();
return View("viewName", model);
}
This will return your original Search criteria and the results. Is probably not all a hundred percent functional but the main ideas are there, and I can help you through problems if you decide to go down this path.
to display the results
#for (int i = 0; i < Model.ResultsAddress.Count; i++)
{
#Html.DisplayFor(model => model.ResultsAddress[i].date)
}
or for just displaying them top one can be used for editing and resubmitting the data.
#foreach (var item in Model.ResultsAddress)
{
<div>#item.date</div>
}
Ryan Schlueter is right that you should use ASP.NET MVC principles such as model, autobinding to model fields in action parameters and so on. But the main in this case you should use strongly typed view:
Action:
public ActionResult SomeAction(ModelClass model)
{
....
return("ViewName", model)
}
View:
#model SomeNamespace.ModelClass
#using (Html.BeginForm("Submit", "Insert", FormMethod.Post))
{
#Html.TextBoxFor(model => model.Property1)
#Html.TextBoxFor(model => model.Property2)
#Html.TextBoxFor(model => model.Property3)
<input id="button" name="button" type="submit" value="submit" />
}