I am using a Kendo editor to create email templates and on POST, once a change to the template has been submitted, always renders in encoded HTML.
This is my razor code on the page:
#model Business.Models.Administration.EmailSetupViewModel
#using Kendo.Mvc.UI;
<h2>Application Stages Portal</h2>
<h4>Email Setup</h4>
#using (Html.BeginForm())
{
if (Model.EmailSaved)
{
<h2>
Email template saved</h2>
}
else
{
#* #Html.DisplayFor(m => m.EmailSavedMsg)*#
}
#Html.DropDownListFor(m => m.EmailTemplateToEdit, Model.EmailTemplatesList)
<input type="submit" name="setup" value="setup" />
if (Model.ShowEmailForm)
{
<div id="email-edit">
#Html.Label("Title")
#Html.TextBoxFor(m => m.EmailTitle, new { style = "width:200px" })
<br />
#(Html.Kendo().Editor()
.Name("editor")
.HtmlAttributes(new { style = "width: 600px;height:440px" })
.Value(#<text>
#Html.Raw(Model.EmailBody)
</text>))
</div>
<input type="submit" id="btnSaveTemplate" name="update" value="update" />
<h2>
Please note</h2>
<p>
The following items are <i>reserved and should not be changed, you may move them
to a different place within the message. </i>
<ul>
<li><*name*> e.g. Fred Flinstone </li>
<li><*membernumber*> e.g. 12345678 </li>
</ul>
</p>
}
}
And this is where the actual editor markup is on the page
#(Html.Kendo().Editor()
.Name("editor")
.HtmlAttributes(new { style = "width: 600px;height:440px" })
.Value(#<text>
#Html.Raw(Model.EmailBody)
</text>))
Model.EmailBody contains the actual string.
When I GET the page, it renders fine. But when I do POST it never decodes so the rendering is wrong. I don't want to see all the HTML tags but the actual formatting.
This is my Controller code:
#region Email template
[HttpGet]
public ActionResult EmailSetup()
{
ViewBag.DisplayName = StaticFunctions.GetDisplayName(this.User.Identity.Name);
EmailSetupViewModel model = new EmailSetupViewModel();
Business.Administration.Email Email = new Business.Administration.Email();
var list = Email.GetTemplateList();
model.EmailTemplatesList = list.OrderBy(o => o.Text).ToList();
return View(model);
}
[HttpPost]
public ActionResult EmailSetup(EmailSetupViewModel model, string value, string editor)
{
ViewBag.DisplayName = StaticFunctions.GetDisplayName(this.User.Identity.Name);
string body = HttpUtility.HtmlDecode(editor); //encode to db
if (Request["update"] != null)
{
Business.Administration.Email Email = new Business.Administration.Email();
model.EmailSaved = Email.SaveTemplate(model, body);
//ModelState.Clear(); // when doing POST - clearing the ModelState will prevent encode of HTML (Default behaviour). This isn't good long term solution.
if (model.EmailSaved)
{
model.EmailSavedMsg = "Template saved";
}
else
{
model.EmailSavedMsg = "Template couldn't be saved";
}
model.EmailTemplatesList = Email.GetTemplateList();
model = Email.GetTemplate(model);
model.EmailBody = HttpUtility.HtmlDecode(model.EmailBody);
return View(model);
}
else
{
Business.Administration.Email Email = new Business.Administration.Email();
model.EmailTemplatesList = Email.GetTemplateList();
model = Email.GetTemplate(model);
model.EmailBody = HttpUtility.HtmlDecode(model.EmailBody);
return View(model);
}
}
#endregion
This is my model, I am using [AllowHtml] attribute on the property.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
namespace Business.Models.Administration
{
public class EmailSetupViewModel
{
public EmailSetupViewModel()
{
this.EmailTemplatesList = new List<SelectListItem>();
}
public string EmailTemplateToEdit { get; set; }
public List<SelectListItem> EmailTemplatesList { get; set; }
public string EmailTitle { get; set; }
[AllowHtml]
public string EmailBody { get; set; }
public bool ShowEmailForm { get; set; }
public bool EmailSaved { get; set; }
public string EmailSavedMsg { get; set; }
}
}
Finally two screenshots, one on GET and one on POST.
I was using ModelState.Clear() as well but when I clicked back on the browser, it wouldn't decode.
So basically I want help rendering the HTML in my editor on post so it renders properly and doesn't show HTML tags in the editor.
Related
I am coding a .Net 5 Blazor server side app and - sorry if I am using the wrong terminology - can't seem to pin down why the razor page's object/property is null, yet the code-behind method that populates that object/property contains data from a webApi. I am trying to use the repository pattern, dto objects, dependency injection, webapi, and efcore.
In Startup.cs > ConfigureServices() I have:
services.AddHttpClient<IDocumentNumbersDataService, DocumentNumbersDataService>
(client => client.BaseAddress = new Uri("https://localhost:44323/"));
For the UI I have Home.razor:
#page "/Home"
<form method="get">
<div class="input-group-append">
<button type="submit" class="btn btn-primary" #onclick="SearchDocumentNumbers">Search</button>
</div></form>
#if (DocumentNumbers == null)
{
<p><em>Loading...</em></p>
}
else
{
```
show table of document numbers using foreach()
```
}
and its code-behind Home.cs:
public partial class Home
{
public IEnumerable<DocumentNumberDto> DocumentNumbers { get; set; }
[Parameter]
public string ProjNumber { get; set; }
[Inject]
public IDocumentNumbersDataService DocumentNumbersDataService { get; set; }
protected async Task<IEnumerable<DocumentNumberDto>> SearchDocumentNumbers()
{
ProjNumber = "1012100100";
var DocumentNumbers = await DocumentNumbersDataService.GetDocumentNumbersAsync(ProjNumber); //DocumentNumbers gets populated with Dto objects
}
}
The call to GetDocumentNumbersAsync() in DocumentNumberDataService is:
public async Task<IEnumerable<DocumentNumberDto>> GetDocumentNumbersAsync(string ProjNumber)
{
return await JsonSerializer.DeserializeAsync<IEnumerable<DocumentNumberDto>>
(await _httpClient.GetStreamAsync($"api/documentnumbers/{ProjNumber}"),
new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
}
The problem: DocumentNumbers property is null in Home.razor even though the IEnumerable<DocumentNumbersDto> DocumentNumbers is populated via...
var DocumentNumbers = await DocumentNumbersDataService.GetDocumentNumbersAsync(ProjNumber);
I suspect I overlooked something simple.
I am building my first web app with .net core razor pages.
I am trying to move my menu to be partial, so I can reuse it as I wish but I must be doing something wrong.
The error is:
The model item passed into the ViewDataDictionary is of type 'BrambiShop.UI.Pages.IndexModel', but this ViewDataDictionary instance requires a model item of type 'BrambiShop.UI.Pages.Shared._SideCategoriesMenuModel'.
I know there are few topics on that, but I am struggling " days after work now and I couldnt find any exact solution. I quess I kinda understand what I am doing wrong. But I dont know how to do it right.
I am trying to load the partial from index as follows:
#page
#model IndexModel
#{
ViewData["Title"] = "Home page";
}
#{ await Html.RenderPartialAsync("_SideCategoriesMenu"); }
The partial is:
#page
#model BrambiShop.UI.Pages.Shared._SideCategoriesMenuModel
#*
For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
*#
#foreach (var category in Model.Categories)
{
<button class="font-weight-bold" data-toggle="collapse" href="#MenuCollapse_#Html.DisplayFor(modelItem => category.Id)"
aria-expanded="false" aria-controls="MenuCollapse_#Html.DisplayFor(modelItem => category.Id)">
#Html.DisplayFor(modelItem => category.Name)
</button>
<!-- Collapsible element -->
<div class="collapse text-center" id="MenuCollapse_#Html.DisplayFor(modelItem => category.Id)">
#foreach (var subCategory in Model.SubCategories.Where(x => x.CategoryId == category.Id))
{
<form action="/url/to/action" Method="GET">
<input type="hidden" name="Property" value="#Html.DisplayFor(modelItem => subCategory.Id)" />
<button type="submit">
<i class="fas fa-caret-right pl-1"></i>#Html.DisplayFor(modelItem => subCategory.Name)
</button>
</form>
}
</div>
<!-- / Collapsible element -->
}
And at last the cshtml.cs
using BrambiShop.API.Models;
using BrambiShop.UI.Services;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace BrambiShop.UI.Pages.Shared
{
public class _SideCategoriesMenuModel : PageModel
{
private readonly IApiClient _Client;
public _SideCategoriesMenuModel(IApiClient client)
{
_Client = client;
}
public IList<Category> Categories { get; set; }
public IList<SubCategory> SubCategories { get; set; }
public async Task OnGet()
{
Categories = await _Client.GetCategoriesAsync();
SubCategories = await _Client.GetSubCategoriesAsync();
}
}
}
Can someone help me with that please? I did watch about 10 hours of tutorials to build api and the ground of the web, but this damn partial. I just cant find answer to that anywhere.
Thank you very much for any effort.
EDIT
--- index model
using BrambiShop.API.Models;
using BrambiShop.UI.Services;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace BrambiShop.UI.Pages
{
public class IndexModel : PageModel
{
public IndexModel()
{
}
public async Task OnGet()
{
}
}
}
Your Index view has a model of type IndexModel. When you put this line
#{ await Html.RenderPartialAsync("_SideCategoriesMenu"); }
The same model will be passed to _SideCategoriesMenu which is IndexModel. This is why you are receiving the exception because the model passed to the _SideCategoriesMenu is different since it expects BrambiShop.UI.Pages.Shared._SideCategoriesMenuModel.
To go around that, you have to specify the model in the RenderPartialAsync method. ex:
#{ await Html.RenderPartialAsync("_SideCategoriesMenu",YOUR_MODEL_HERE); }
You can replace YOUR_MODEL_HERE with whatever instance of type _SideCategoriesMenuModel. You can have it as a property inside IndexModel. ex:
public class IndexModel : PageModel
{
public IndexModel()
{
}
public SideMenuModel SideMenu { get; set;}
public BrambiShop.UI.Pages.Shared._SideCategoriesMenuModel SideMenuModel{ get; set;}
public async Task OnGet()
{
}
}
}
And then use it in your index view
#page
#model IndexModel
#{
ViewData["Title"] = "Home page";
}
#{ await Html.RenderPartialAsync("_SideCategoriesMenu",Model.SideMenuModel); }
The model you use to create the page needs to contain a complete model for the Menu. Then you need to pass the model in the RenderPartial.
IE:
#{ await Html.RenderPartialAsync("_SideCategoriesMenu", Model.SideMenu); }
Edit--
Add the SideMenu property to your Model so you can access it on the page in the above example.
using BrambiShop.API.Models;
using BrambiShop.UI.Services;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace BrambiShop.UI.Pages
{
public class IndexModel : PageModel
{
public IndexModel()
{
}
public SideMenuModel SideMenu { get; set;}
public async Task OnGet()
{
}
}
}
I currently have an ASP.NET Core 2.0 Web Application (built in VS 2017 from the default web application template). I have a RazorPage with a dropdown built in that I would like to populate via a SQL Query. I've used Entity Framework Core to build a model of my database as follows:
public partial class INVOPEContext : DbContext
{
public virtual DbSet<PmeFundData> PmeFundData { get; set; }
modelBuilder.HasAnnotation"Relational:DefaultSchema", "Server\\User");
modelBuilder.Entity<PmeFundData>(entity =>
{
entity.ToTable("PME_FUND_DATA", "dbo");
entity.Property(e => e.Firm).HasMaxLength(255);
});
}
public partial class PmeFundData
{
public string Firm { get; set; }
}
I've updated the RazorPage PageModel (pmepe.cshtml.cs) to include the DBContext and Query:
public class pmepeModel : PageModel
{
private readonly INVOPEContext _db;
public pmepeModel(INVOPEContext db)
{
_db = db;
}
public IActionResult dropdowns()
{
List<PmeFundData> firmList = new List<PmeFundData>();
firmList = (from Firm in _db.PmeFundData
select Firm).Distinct().ToList();
firmList.Insert(0, new PmeFundData {Firm = "All Firms" });
ViewBag.ListofFirms = firmList;
return View();
}
}
Finally, the view with the dropdown (pmepe.cshtml) is as follows:
#page
#model pmepeModel
#{
ViewData["Title"] = "pmepe";
}
<select asp-for="dropdowns"
id="firm"
class="dropdown"
asp-items= "#(new SelectList(ViewBag.ListofFirms, "Firm"))">
</select>
I am getting errors that neither ViewBag nor View exist in the current context in the PageModel (no errors in the view - Intellisense picks it up). Every example I've found on the web is for MVC rather than RazorPages. The solution commonly provided for MVC is imbedding the query in a Controller and adjusting the MVC version in the web.config file. But the RazorPages template doesn't have Controllers and I can't find a web.config file - so I haven't been able to get it to work in my app. Any help you can provide would be most appreciated.
There are multiple issues in your code.
PmeFundData should have Id property, otherwise, you will receive error while running update-database command.
public partial class PmeFundData
{
public int Id { get; set; }
public string Firm { get; set; }
}
ViewBag is not supported in RazorPage, you could trace this issue from Add ViewBag to PageModel #6754, you could try ViewData or PageModel Property to bind the object.
Here is a simple code for ViewData.
public class PmepeModelModel : PageModel
{
private readonly CoreRazor2_1.Data.ApplicationDbContext _context;
public PmepeModelModel(CoreRazor2_1.Data.ApplicationDbContext context)
{
_context = context;
}
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
public int SelectedFirm { get; set; }
[ViewData]
public IList<PmeFundData> ListofFirms { get {
return Dropdowns();
}
}
public IList<PmeFundData> Dropdowns()
{
List<PmeFundData> firmList = new List<PmeFundData>();
firmList = new List<PmeFundData> {
new PmeFundData{ Id = 1, Firm = "F1"},
new PmeFundData{ Id = 2, Firm = "F3"},
new PmeFundData{ Id = 3, Firm = "F2"}
};
//firmList = (from Firm in _context.PmeFundData
// select Firm).Distinct().ToList();
firmList.Insert(0, new PmeFundData { Firm = "All Firms" });
return firmList;
//ViewData["ListofFirms"] = firmList;
}
public async Task<IActionResult> OnPostAsync()
{
var value = SelectedFirm;
if (!ModelState.IsValid)
{
return Page();
}
_context.PmeFundData.Add(PmeFundData);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
View
#page
#model CoreRazor2_1.Pages.PmepeModelModel
#{
ViewData["Title"] = "PmepeModel";
}
<h2>PmepeModel</h2>
<h4>PmeFundData</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<select asp-for="#Model.SelectedFirm"
class="dropdown"
asp-items="#(new SelectList((IEnumerable<PmeFundData>)#ViewData["ListofFirms"], "Id" ,"Firm"))">
</select>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
You may also learn Razor Page from Introduction to Razor Pages in ASP.NET Core
MVC6 introduces Tag Helpers which is a better way compared to using #Html.EditorFor, etc. However I have not found any Tag Helper that would be an alternative to #Html.DisplayFor.
Of course I can use a variable directly on a Razor page, such as #Model.BookingCode. But this does not allow to control formatting.
With MVC6, what's conceptually correct way for displaying a value of a model property?
#Html.DisplayFor still exists and can still be used.
The difference between HtmlHelpers and TagHelpers is that HtmlHelpers choose which html elements to render for you whereas TagHelpers work with html tags that you add yourself so you have more full control over what html element is used. You do have some control over the markup using templates with HtmlHelpers but you have more control with TagHelpers.
So you should think in terms of what html markup do I want to wrap this model property in and add that markup around the property itself using #Model.Property with some markup around it or continue using DisplayFor if you prefer to let the helper decide.
You can create your own tag helper
namespace MyDemo.TagHelpers
{
[HtmlTargetElement("p", Attributes = ForAttributeName)]
public class DisplayForTagHelper : TagHelper
{
private const string ForAttributeName = "asp-for";
[HtmlAttributeName(ForAttributeName)]
public ModelExpression For { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (output == null)
{
throw new ArgumentNullException(nameof(output));
}
var text = For.ModelExplorer.GetSimpleDisplayText();
output.Content.SetContent(text);
}
}
}
Add use it in view:
<p asp-for="MyProperty" class="form-control-static"></p>
I have been using this as a display tag helper.
[HtmlTargetElement("*", Attributes = ForAttributeName)]
public class DisplayForTagHelper : TagHelper
{
private const string ForAttributeName = "asp-display-for";
private readonly IHtmlHelper _html;
public DisplayForTagHelper(IHtmlHelper html)
{
_html = html;
}
[HtmlAttributeName(ForAttributeName)]
public ModelExpression Expression { get; set; }
public IHtmlHelper Html
{
get
{
(_html as IViewContextAware)?.Contextualize(ViewContext);
return _html;
}
}
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (output == null)
throw new ArgumentNullException(nameof(output));
var type = Expression.Metadata.UnderlyingOrModelType;
if (type.IsPrimitive)
{
output.Content.SetContent(Expression.ModelExplorer.GetSimpleDisplayText());
}
// Special Case for Personal Use
else if (typeof(Dictionary<string, string>).IsAssignableFrom(type))
{
output.Content.SetHtmlContent(Html?.Partial("Dictionary", Expression.ModelExplorer.Model));
}
else
{
var htmlContent = Html.GetHtmlContent(Expression);
output.Content.SetHtmlContent(htmlContent);
}
}
}
public static class ModelExpressionExtensions
{
public static IHtmlContent GetHtmlContent(this IHtmlHelper html, ModelExpression expression)
{
var ViewEngine = html.ViewContext.HttpContext.RequestServices.GetService(typeof(ICompositeViewEngine)) as ICompositeViewEngine;
var BufferScope = html.GetFieldValue<IViewBufferScope>();
var htmlContent = new TemplateBuilder(ViewEngine, BufferScope, html.ViewContext, html.ViewContext.ViewData, expression.ModelExplorer, expression.Name, null, true, null).Build();
return htmlContent;
}
public static TValue GetFieldValue<TValue>(this object instance)
{
var type = instance.GetType();
var field = type.GetFields(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance).FirstOrDefault(e => typeof(TValue).IsAssignableFrom(e.FieldType));
return (TValue)field?.GetValue(instance);
}
}
try below code
public class movie
{
public int ID { get; set; }
[DisplayName("Movie Title")]
public string Title { get; set; }
}
///////////////////////////////////////////////////
#model IEnumerable<MvcMovie.Models.Movie>
<h1>Show List Movies</h1>
<label asp-for="ToList()[0].Title">< /label>
#foreach (var movie in Model)
{
#movie.Title
}
In my webapp
webApp
\Views
\Views\School
\Views\School\School.cshtml
\Views\School\Schools.cshtml
In Request and Response classes:
[Route("/v1/school", Verbs = "POST")]
[DefaultView("School")]
public class SchoolAddRequest : School, IReturn<SchoolResponse>
{
}
public class SchoolResponse
{
public School School { get; set; }
public SchoolResponse()
{
ResponseStatus = new ResponseStatus();
Schools = new List<School>();
}
public List<School> Schools { get; set; }
public ResponseStatus ResponseStatus { get; set; }
}
In SchoolService.cs:
[DefaultView("School")]
public class SchoolService: Service
{
public SchoolResponse Post(SchoolAddRequest request)
{
var sch = new School {Id = "10"};
return new SchoolResponse {School = sch, ResponseStatus = new ResponseStatus()};
}
}
In school.cshtml:
#inherits ViewPage<Test.Core.Services.SchoolResponse>
#{
Layout = "_Layout";
}
<form action="/v1/School" method="POST">
#Html.Label("Name: ") #Html.TextBox("Name")
#Html.Label("Address: ") #Html.TextBox("Address")
<button type="submit">Save</button>
</form>
#if (#Model.School != null)
{
#Html.Label("ID: ") #Model.School.Id
}
On the browser:
This is suppose to work but it is not, i get a blank page
http://test/school/
This works:
http://test/views/school/
On hitting the 'save' btn the required response is returned but the url on the browser is :
http://test/v1/School
I was expecting it to be:
http://test/School
How can i get the url to work right.? Shouldn't it be
http://test/School on request and response.
http://test/school/ is not returning anything because you don't have a request DTO and a corresponding 'Get' service implemented for the route.
What you need is a request DTO:
[Route("/school", Verbs = "GET")]
public class GetSchool : IReturn<SchoolResponse>
{
}
and the service...
public SchoolResponse Get(GetSchool request)
{
var sch = new School {Id = "10"};
return new SchoolResponse {School = sch, ResponseStatus = new ResponseStatus()};
}
When you hit 'Save', a 'POST' request will be made to the server through the route 'v1/school' because the form tag you specified has:
<form action="/v1/School" method="POST">
Hope this helps.