I am using ASP.NET Core and Mailkit to send emails. Take the following (Basic) code:
var bodyBuilder = new BodyBuilder();
bodyBuilder.HtmlBody = GetBody();
var m = new MimeMessage();
m.To.Add(new MailboxAddress("gurdip.sira#gmail.com"));
m.From.Add(new MailboxAddress("Sender Name", "gurdip.sira#gmail.com"));
string s = GetBody();
// m.Body = bodyBuilder.ToMessageBody();
m.Body = new TextPart(MimeKit.Text.TextFormat.Html) {Text = s};
using (var smtp = new MailKit.Net.Smtp.SmtpClient())
{
smtp.Connect("smtp.gmail.com", 587);
smtp.AuthenticationMechanisms.Remove("XOAUTH2");
smtp.Authenticate("gurdip.sira#gmail.com", "December5!");
smtp.Send(m);
}
The GetBody() method just reads a html document (streamreader).
What I'd like to do is use razor views and cshtml as my emails may contain dynamic content (e.g. an unknown sized collection of certain items).
I can't seem to find definitive documentation on how to do this. The idea is to then just read the cshtml view as plain html but resolve the razor syntax and model variables.
Anyone done anything like this?
One solution is from your controller to pass the content.
public void TestAction(){
var content = PartialView("your_partial_view").ToString();
your_SendEmailFunction(content)
}
So basically you use the partial view as a string that you pass as a content to your method.
Here is a simple demo based on jmal73's comment in Paris Polyzos' blog like below:
1.custom interface:
public interface IViewRenderService
{
Task<string> RenderToStringAsync(string viewName, object model);
}
2.implement interface:
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
public class ViewRenderService : IViewRenderService
{
private readonly IRazorViewEngine _razorViewEngine;
private readonly ITempDataProvider _tempDataProvider;
private readonly HttpContext _httpContext;
public ViewRenderService(IRazorViewEngine razorViewEngine,
ITempDataProvider tempDataProvider,
IHttpContextAccessor httpContextAccessor)
{
_razorViewEngine = razorViewEngine;
_tempDataProvider = tempDataProvider;
_httpContext = httpContextAccessor.HttpContext;
}
public async Task<string> RenderToStringAsync(string viewName, object model)
{
var actionContext = new ActionContext(_httpContext, new RouteData(), new ActionDescriptor());
var viewEngineResult = _razorViewEngine.FindView(actionContext, viewName, false);
if (viewEngineResult.View == null || (!viewEngineResult.Success))
{
throw new ArgumentNullException($"Unable to find view '{viewName}'");
}
var view = viewEngineResult.View;
using (var sw = new StringWriter())
{
var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary());
viewDictionary.Model = model;
var tempData = new TempDataDictionary(_httpContext, _tempDataProvider);
var viewContext = new ViewContext(actionContext, view, viewDictionary, tempData, sw, new HtmlHelperOptions());
viewContext.RouteData = _httpContext.GetRouteData(); //set route data here
await view.RenderAsync(viewContext);
return sw.ToString();
}
}
}
3.read .cshtml file and return string:
public class HomeController : Controller
{
private readonly IViewRenderService _viewRenderService;
public HomeController(IViewRenderService viewRenderService)
{
_viewRenderService = viewRenderService;
}
public IActionResult Index()
{
var data = new Users()
{
UserId = 1
};
return View(data);
}
public async Task<IActionResult> Privacy()
{
var data = new Users()
{
UserId = 1
};
var result = await _viewRenderService.RenderToStringAsync("Home/Index", data);
return Content(result);
}
4.Index.cshtml:
#model Users
<form>
<label asp-for="UserId"></label>
<br />
<input asp-for="UserId" class="form-control" maxlength="4" />
<span asp-validation-for="UserId" class="text-danger"></span>
<input type="submit" value="create" />
</form>
5.Register service:
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IViewRenderService, ViewRenderService>();
Related
I'm trying to use NavivgateTo in Blazor to pass a file id and name to download a file from my Download controller.
What is the proper setup? I've tried a number of possibilities and I keep seeing an error: Sorry, there is nothing at this address.
Razor Page
public async Task SelectedDisplayDbItemChanged(DisplayDbItemsComboBoxItemDTO item)
{
Data = null;
Data = GetDataTable();
var fileId = await utilities.ExportDataTableToFile((DataTable)Data).ConfigureAwait(false);
//navigationManager.NavigateTo($"api/download/fileId/" + fileId + "/fileName/" + "myfile", true);
//?data1=678&data2=c-sharpcorner
navigationManager.NavigateTo($"api/Download/{fileId}/{"myfile"}", true);
}
Controller:
[HttpPost("Download/{fileId}/{fileName}")]
public async Task<IActionResult> Download(string fileId, string fileName)
{
using (var ms = new MemoryStream())
{
var fullPath = Path.Combine(DownloadPath, fileId);
await using (var stream = new FileStream(fullPath, FileMode.Open))
{
await stream.CopyToAsync(ms);
}
ms.Position = 0;
return File(ms, "application/octet-stream", $"{fileName}.xlsx");
}
}
I've seen a lot of examples from the Razor page to the Razor page, but not from NavigateTo to a controller with passing multiple parameters.
I've tried these responses as well: https://stackoverflow.com/a/71130256/9594249
https://stackoverflow.com/a/71130256/9594249
Not like Asp.net MVC or razor page, in Blazor parameters are passed by [Parameter] tag
#page "/Download/{fileId}/{fileName}"
#code {
[Parameter]
public string? fileId { get; set; }
[Parameter]
public string? fileName { get; set; }
}
please refer : https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/routing?view=aspnetcore-6.0
(Updated)
add to Program.cs or Startup.cs:
builder.Services.AddRazorPages(options => {
options.Conventions.AddPageRoute("/DownloadPage", "Download/{fileId?}/{fileName?}");
}
});
Pages/DownloadPage.cshtml
#page "{fileId?}/{fileName?}"
#model BlazorApp.Pages.DownloadModel
Pages/DownloadPage.cshtml.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace BlazorApp.Pages;
public class DownloadModel : PageModel
{
private readonly IWebHostEnvironment _env;
public DownloadModel(IWebHostEnvironment env)
{
_env = env;
}
public IActionResult OnGet()
{
// work with RouteData.Values["fileId"] and RouteData.Values["fileName"]
}
}
please refer :
https://learn.microsoft.com/en-us/answers/questions/243420/blazor-server-app-downlaod-files-from-server.html
https://learn.microsoft.com/ko-kr/aspnet/core/razor-pages/razor-pages-conventions?view=aspnetcore-6.0
I need to fill a form with inputs in Xamarin, and send them to my API page.
I already tried sending the data in "Postman" and it saved it correctly, but I would like to know how to send it from Xamarin.
Attention, I can read the data correctly from the application.
public class FuelPurchase
{
public int id { get; set; }
public string date{ get; set; }
public int vueltametal { get; set; }
public int amount{ get; set; }
public string station{ get; set; }
public string location{ get; set; }
}
And the form you create in Xamarin is this.
<Label Text="Fuel Purchase"/>
<Label Text="Fecha">
<DatePicker x:Name="Date"/>
<Label Text="Station"/>
<Entry x:Name="Station"/>
<Label Text="Location"/>
<Entry x:Name="Location"/>
<Label Text="Amount"/>
<Entry x:Name="amount" Keyboard="Numeric"/>
Here is a static class that I use for API's. You can change the url to match yours if you only have one. Make sure to step through it and check that all of your "/"'s are in the right spot!
using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using NGJS = System.Text.Json;
using System.Threading.Tasks;
namespace TestBCS
{
public class RestService
{
readonly HttpClient client;
public RestService()
{
client = new HttpClient(new HttpClientHandler
{
Proxy = null,
UseProxy = false
});
}
#region GET
public async Task<object> RefreshDataAsync(string url, string qs)
{
Uri uri = new Uri(string.Format(url, qs));
HttpResponseMessage response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
using (var stream = await response.Content.ReadAsStreamAsync())
{
return await NGJS.JsonSerializer.DeserializeAsync<object>(stream);
}
}
//Error Handling here
return null;
}
#endregion
#region POST
static void SerializeJsonIntoStream(object value, Stream stream)
{
using (var sw = new StreamWriter(stream, new UTF8Encoding(false), 1024, true))
using (var jtw = new JsonTextWriter(sw) { Formatting = Formatting.None })
{
var js = new JsonSerializer();
js.Serialize(jtw, value);
jtw.Flush();
}
}
HttpContent CreateHttpContent(object content)
{
HttpContent httpContent = null;
if (content != null)
{
var ms = new MemoryStream();
SerializeJsonIntoStream(content, ms);
ms.Seek(0, SeekOrigin.Begin);
httpContent = new StreamContent(ms);
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
return httpContent;
}
public async Task PostStreamAsync(string url, object content)
{
string Url = url;
using (var client = new HttpClient())
using (var request = new HttpRequestMessage(HttpMethod.Post, Url))
using (var httpContent = CreateHttpContent(content))
{
request.Content = httpContent;
using (var response = await client
.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
.ConfigureAwait(false))
{
response.EnsureSuccessStatusCode();
Debug.WriteLine("Successfully Sent");
}
}
}
#endregion
}
}
I have an application that uses Markdown in Razor Pages (compiled using a custom TagHelper) to display static content. There are about 300 pages worth.
I'm trying to render these Razor pages to html so that I can index the content using Lucene.net, in order to provide full text search.
I've managed to create some code (see below) that works, but prepends previously rendered content whenever subsequent pages are rendered. That is, when I render the first page it works perfectly but, when I render another page the first page's content is also included and, when I render a third page the first two pages content are included.
I have tried creating the dependencies manually within the method, and I have tried creating a new instance of this class for each page, but always with the same result.
What am I missing?
public class RazorPageRenderer
{
private readonly IRazorViewEngine razorViewEngine;
private readonly ITempDataProvider tempDataProvider;
private readonly IHttpContextAccessor httpContextAccessor;
private readonly IActionContextAccessor actionContextAccessor;
private readonly IRazorPageActivator razorPageActivator;
private readonly ILogger logger;
public RazorPageRenderer(
IRazorViewEngine razorViewEngine,
ITempDataProvider tempDataProvider,
IHttpContextAccessor httpContextAccessor,
IActionContextAccessor actionContextAccessor,
IRazorPageActivator razorPageActivator,
ILogger<RazorPageRenderer> logger)
{
this.razorViewEngine = razorViewEngine;
this.tempDataProvider = tempDataProvider;
this.httpContextAccessor = httpContextAccessor;
this.actionContextAccessor = actionContextAccessor;
this.razorPageActivator = razorPageActivator;
this.logger = logger;
}
public async Task<string> RenderAsync(IRazorPage razorPage) => await RenderAsync<object>(razorPage, null);
public async Task<string> RenderAsync<T>(IRazorPage razorPage, T model)
{
try
{
var viewDataDictionary = CreateViewDataDictionary(razorPage.GetType(), model);
await using var writer = new StringWriter();
var view = new RazorView(
razorViewEngine,
razorPageActivator,
Array.Empty<IRazorPage>(),
razorPage,
HtmlEncoder.Default,
new DiagnosticListener(nameof(RazorPageRenderer)));
var viewContext = new ViewContext(
actionContextAccessor.ActionContext,
view,
viewDataDictionary,
new TempDataDictionary(
httpContextAccessor.HttpContext,
tempDataProvider),
writer,
new HtmlHelperOptions());
if (razorPage is Page page)
{
page.PageContext = new PageContext(actionContextAccessor.ActionContext)
{
ViewData = viewContext.ViewData
};
}
razorPage.ViewContext = viewContext;
razorPageActivator.Activate(razorPage, viewContext);
await razorPage.ExecuteAsync();
return writer.ToString();
}
catch (Exception exception)
{
logger.LogError(
"An exception occured while rendering page: {Page}. Exception: {Exception}",
razorPage.Path,
exception);
}
return null;
}
private static ViewDataDictionary CreateViewDataDictionary(Type pageType, object model)
{
var dictionaryType = typeof(ViewDataDictionary<>)
.MakeGenericType(pageType);
var ctor = dictionaryType.GetConstructor(new[]
{typeof(IModelMetadataProvider), typeof(ModelStateDictionary)});
var viewDataDictionary = (ViewDataDictionary)ctor?.Invoke(
new object[] {new EmptyModelMetadataProvider(), new ModelStateDictionary()});
if (model != null && viewDataDictionary != null)
{
viewDataDictionary.Model = model;
}
return viewDataDictionary;
}
}
I am new .asp.net core. I am testing a controller that renders a view to a string and then utilises evo pdf to render the view.
All is working perfectly and I am also able to successfully test using postman.
However my test app errors when I use vs 2017 test explorer to debug my test (Xunit).
Searched Locations within the razor engine
The error occurs within my RenderViewToString method as my razor view engine is unable to locate the view to render. The paths searched to locate the views are as expected. Any guidance is appreciated.
//Unit Test Code
[Fact]
public async void GetPdf()
{
var response = await _client.PostAsJsonAsync<Common.DTO.Invoice>("/api/values/1", GetDummyData());
using (var file = System.IO.File.Create(#"c:\\Test" + DateTime.Now.ToString("yyyyyMMddHHmmss") + ".pdf"))
{
//create a new file to write to
await response.Content.CopyToAsync(file);
await file.FlushAsync(); // flush back to disk before disposing
}
}
//Render view to string service
public interface IViewRenderService
{
Task<string> RenderToStringAsync(string viewName, ViewDataDictionary viewData);
}
public class ViewRenderService : IViewRenderService
{
private readonly IRazorViewEngine _razorViewEngine;
private readonly ITempDataProvider _tempDataProvider;
private readonly IServiceProvider _serviceProvider;
public ViewRenderService(IRazorViewEngine razorViewEngine,ITempDataProvider tempDataProvider,IServiceProvider serviceProvider)
{
_razorViewEngine = razorViewEngine;
_tempDataProvider = tempDataProvider;
_serviceProvider = serviceProvider;
}
public async Task<string> RenderToStringAsync(string viewName, ViewDataDictionary viewData)
{
var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
using (var sw = new StringWriter())
{
var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);
if (viewResult.View == null)
{
throw new ArgumentNullException($"{viewName} does not match any available view");
}
var viewContext = new ViewContext(
actionContext,
viewResult.View,
viewData,
new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
sw,
new HtmlHelperOptions()
);
await viewResult.View.RenderAsync(viewContext);
return sw.ToString();
}
}
}
I was getting the same error with core 2.0. The problem is RazorViewEngine is not working as expected with empty RouteData object;
So i injected IHttpContextAccessor and got HttpContext and RouteData from it;
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IViewRenderService, ViewRenderService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddMvc();
}
RazorToStringHelper.cs:
public interface IViewRenderService
{
Task<string> RenderToStringAsync(string viewName, object model);
}
public class ViewRenderService : IViewRenderService
{
private readonly IRazorViewEngine _razorViewEngine;
private readonly ITempDataProvider _tempDataProvider;
private readonly IHttpContextAccessor _httpContextAccessor;
public ViewRenderService(
IRazorViewEngine razorViewEngine,
IHttpContextAccessor httpContextAccessor,
ITempDataProvider tempDataProvider)
{
_razorViewEngine = razorViewEngine;
_tempDataProvider = tempDataProvider;
_httpContextAccessor = httpContextAccessor;
}
public async Task<string> RenderToStringAsync(string viewName, object model)
{
var httpContext = _httpContextAccessor.HttpContext;
var actionContext = new ActionContext(httpContext, httpContext.GetRouteData(), new ActionDescriptor());
var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);
if (viewResult.View == null)
{
throw new ArgumentNullException($"{viewName} does not match any available view");
}
using (var sw = new StringWriter())
{
var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
{
Model = model
};
var viewContext = new ViewContext(
actionContext,
viewResult.View,
viewDictionary,
new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
sw,
new HtmlHelperOptions()
);
await viewResult.View.RenderAsync(viewContext);
return sw.ToString();
}
}
}
Due to time constraints I abandoned the XUnit approach, wrote a test app and also
utilised postman as this was an api requirement to render a pdf from a razor view.
I want to take a few post query parameters from an API i have and create a new entry. I wanted to do this with in the method with out needing to load context or something.
namespace fais.printing_services.Controllers
{
[Produces("application/json")]
[Route("api/[controller]/[action]")]
public class printController : Controller
{
private readonly IHostingEnvironment _appEnvironment;
public printController(IHostingEnvironment appEnvironment)
{
_appEnvironment = appEnvironment;
}
/**/
[HttpPost]
public IActionResult request(string id="test_default", string url = "", string html = "")
{
print_job _print_job = new print_job();
_print_job.html = html;
_print_job.options = options; //json object string
_print_job.url = url;
using (ApplicationDbContext db = new ApplicationDbContext())
{
db.print_job.Add(_print_job);
db.SaveChanges();
}
return Json(new
{
save = true
});
}
}
}
I just want to be able create a new print_job entry and save it when the API is called and return a json response.
Add ApplicationDbContext to controller constructor, it will be injected automatically (if your Startup.cs is like recommeneded):
private readonly IHostingEnvironment _appEnvironment;
private readonly ApplicationDbContext _db;
public printController(IHostingEnvironment appEnvironment, ApplicationDbContext db)
{
_appEnvironment = appEnvironment;
_db = db;
}
[HttpPost]
public IActionResult request(string id="test_default", string url = "", string html = "")
{
var _print_job = new print_job()
{
html = html,
options = options,
url = url,
}
_db.print_job.Add(_print_job);
_db.SaveChanges();
return Json(new { save = true });
}