I would like to have every result of any AJAX call on ASP.NET MVC to be enveloped to a JSON object which should be like:
AjaxResult { status, data }
where status will contain a enumeration value describing if the call was successful, erroneous, authentication expired etc. This will enable client side code to be able to redirect to the login page etc.
I tried catching Ajax Requests by overriding OnActionExecuted, and trying to render the returned by the corresponding action result using the following code, but this solution seems operating slow. Do you have some better idea?
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception == null)
{
if (filterContext.Result.GetType() == typeof(ViewResult))
{
ViewResult viewResultTemp = (ViewResult)filterContext.Result;
using (StringWriter sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewResultTemp.ViewName);
ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
var ajaxReply = new AjaxReply(AjaxReplyStatus.Success, string.Empty, sw.ToString());
filterContext.Result = new JsonResult {Data = ajaxReply};
}
}
else if (filterContext.Result.GetType() == typeof(PartialViewResult))
{
PartialViewResult partialViewResultTemp = (PartialViewResult)filterContext.Result;
using (StringWriter sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, partialViewResultTemp.ViewName);
ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
var ajaxReply = new AjaxReply(AjaxReplyStatus.Success, string.Empty, sw.ToString());
filterContext.Result = new JsonResult { Data = ajaxReply };
}
}
else if (filterContext.Result.GetType() == typeof(JsonResult))
{
JsonResult jsonResult = (JsonResult)filterContext.Result;
JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();
string jsonData = javaScriptSerializer.Serialize(jsonResult.Data);
var ajaxReply = new AjaxReply(AjaxReplyStatus.Success, string.Empty, jsonData);
filterContext.Result = new JsonResult { Data = ajaxReply };
}
}
}
Why you need this?
Create your custom ApplicationController and derive all controllers from this one.
In ApplicationController implement the method Json<data>() where data
public JsonResult Json<TData>(TData data, bool status) where TData : class
{
return Json(
new
{
data,
status
},
JsonRequestBehavior.AllowGet);
}
Do you really need to do that at all?
If your ajax call succeeds than HTTP 200 will be returned and your success jQuery callback will be called. If your call fails than just throw an exception and let jQuery call error callback after it received HTTP 500 from the server.
HTTP status codes are the proper way to inform the caller if the call has succeeded or failed for a certain reason.
Related
I want to refresh a partial view (main) with a single call to reduce the traffic on the network. That one partial view have multiple more partial view (child) inside it. However, I cannot use standard mainpartialview.html(response) approach because one of the child partial view have conflict with the script file. I want to proceed with using JSON however the value inside the model for languageForm and translationForm is empty.The suggestion that I found is require me to use microsoft.aspnetcore.mvc.viewfeatures however it not for Net 6.
Old approach
retun PartialView("~/Views/Facility/Translation/_PanelMain.cshtml", result);
New approach
return Json(new
{
languageForm = PartialView("~/Views/Facility/Translation/_Form_Language.cshtml", result.record),
translationForm = PartialView("~/Views/Facility/Translation/_PanelSub.cshtml", result.subRecord),
translationViewModel = result.subRecord
});
From the accepted answer, below is the full solution
C# Controller
[HttpGet]
public async Task<ActionResult> GetLanguageRecord(long? languageID)
{
LoadViewBag();
var loadOptions = GetDefaultDataSourceLoadOptions();
var languageList = _languageService.GetGridViewList(GetMembershipID(), null, loadOptions);
var translationList = _translationService.GetGridViewList(GetMembershipID(), languageID, loadOptions);
var result = PopulateViewModel(languageID ?? 0, 0, languageList, translationList, loadOptions);
result.record.TransactionTypeID = SubmissionType.Edit;
var services = HttpContext.RequestServices;
var options = services.GetRequiredService<IOptions<MvcViewOptions>>().Value;
var executor = (PartialViewResultExecutor)services.GetRequiredService<IActionResultExecutor<PartialViewResult>>();
return Json(new
{
languageForm = await Render(
options,
ControllerContext,
PartialView("~/Views/Facility/Translation/_Form_Language.cshtml", result.record),
executor),
translationForm = await Render(
options,
ControllerContext,
PartialView("~/Views/Facility/Translation/_PanelSub.cshtml", result.subRecord),
executor),
translationViewModel = result.subRecord
});
}
Javascript
function grdMain_Language_selection_changed(selectedItems) {
var data = selectedItems.selectedRowsData[0];
if (data) {
$.ajax({
type: "GET",
url: "Translation/GetLanguageRecord",
data: { languageID: data.ID },
cache: false,
success: function (response) {
document.getElementById("_EntryForm_Language").innerHTML = response.languageForm;
document.getElementById("_SubPanel").innerHTML = response.translationForm;
$('#_subPanel_body').collapse('hide');
$('#_mainPanel_body').collapse('show');
myData_Translation = response.translationViewModel.recordList;
myTotalCount_Translation = response.translationViewModel.recordTotal;
let gvElement_Translation = document.getElementById("grdSub_Translation");
let gvInstance_Translation = DevExpress.ui.dxDataGrid.getInstance(gvElement_Translation);
gvInstance_Translation.refresh();
}
});
$(window).scrollTop(0);
}
}
After a quick look through the internal implementation for return PartialView("viewname");. I think this is the minimum you need to render a partial view into a string;
public async Task<string> Render(MvcViewOptions options, ActionContext context, PartialViewResult result, PartialViewResultExecutor executor)
{
var viewEngineResult = executor.FindView(context, result);
viewEngineResult.EnsureSuccessful(originalLocations: null);
var view = viewEngineResult.View;
using (view as IDisposable)
{
using var writer = new StringWriter();
var viewContext = new ViewContext(
context,
view,
result.ViewData,
result.TempData,
writer,
options.HtmlHelperOptions);
await view.RenderAsync(viewContext);
await writer.FlushAsync();
return writer.ToString();
}
}
Which you would use like;
var services = HttpContext.RequestServices;
var options = services.GetRequiredService<IOptions<MvcViewOptions>>().Value;
var executor = (PartialViewResultExecutor)services.GetRequiredService<IActionResultExecutor<PartialViewResult>>();
return Json(new {
languageForm = await Render(
options,
ControllerContext,
PartialView("~/Views/Facility/Translation/_Form_Language.cshtml", result.record),
executor),
...
});
With a bit more effort, you could probably use a Utf8JsonWriter to render each view html directly to the response body without any caching.
In C# MVC 5, is there any built in way to create email templates from .cshtml files?
I'd like to compile the .cshtml based on the model we pass in, thus creating a raw .html string.
It appears the compilation is not callable in C# MVC. We need the raw HTML output because we're trying to send emails.
If possible, avoid 3rd party libraries in the answers - trying to stick within vanilla C#.
What I've tried
Here-strings could be a possible solution, but they could get messy and hard to read. They also wouldn't show the same IDE linting as having a .cshtml file.
Absolutely you can do this yourself.
Options 1: use _partialViews with a dynamic model and renderpartial to or the option to render to HTMLstring #Html.Partial(_PartialView,(ModelClass)View.Data)
Option 2: build your template based on the user/tenant/selection on the server side.
This example is getting ready to send out a confirmation on registration, and inside the comments you can see where you can switch/load different HTML temaplates
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
// var user = await _userManager.FindByEmailAsync(model.Email);
if (result.Succeeded)
{
// Send an email with this link
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Action(nameof(ConfirmEmail), "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
//Email from Email Template
string Message = "Please confirm your account by clicking here";
// string body;
var webRoot = _env.WebRootPath; //get wwwroot Folder
//Get TemplateFile located at wwwroot/Templates/EmailTemplate/Register_EmailTemplate.html
var pathToFile = _env.WebRootPath
+ Path.DirectorySeparatorChar.ToString()
+ "Templates"
+ Path.DirectorySeparatorChar.ToString()
+ "EmailTemplate"
+ Path.DirectorySeparatorChar.ToString()
+ "Confirm_Account_Registration.html";
var subject = "Confirm Account Registration";
var builder = new BodyBuilder();
using (StreamReader SourceReader = System.IO.File.OpenText(pathToFile))
{
builder.HtmlBody = SourceReader.ReadToEnd();
}
//{0} : Subject
//{1} : DateTime
//{2} : Email
//{3} : Username
//{4} : Password
//{5} : Message
//{6} : callbackURL
string messageBody = string.Format(builder.HtmlBody,
subject,
String.Format("{0:dddd, d MMMM yyyy}", DateTime.Now),
model.Email,
model.Email,
model.Password,
Message,
callbackUrl
);
await _emailSender.SendEmailAsync(model.Email, subject, messageBody);
ViewData["Message"] = $"Please confirm your account by clicking this link: <a href='{callbackUrl}' class='btn btn-primary'>Confirmation Link</a>";
ViewData["MessageValue"] = "1";
_logger.LogInformation(3, "User created a new account with password.");
return RedirectToLocal(returnUrl);
}
ViewData["Message"] = $"Error creating user. Please try again later";
ViewData["MessageValue"] = "0";
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
This is a method I use to strongly type a model to a .cshtml and get Razor to render html as a variable I can use with email bodies.
public string ProcessView<TModel>(string viewName, object model, ControllerContext controllerContext, ViewDataDictionary viewData, TempDataDictionary tempData)
{
Check.NullOrEmpty(viewName, "viewName", "View name is required.");
Check.Null(viewData, "viewData", "It might be nothing, but view data was null and the original .net code has a special case for it.");
var dictionary = new ViewDataDictionary(viewData)
{
Model = model,
};
using (var writer = new System.IO.StringWriter())
{
var hostView = new RazorView(controllerContext, "nothing", String.Empty, false, Enumerable.Empty<string>());
var tempContext = new ViewContext(controllerContext, hostView, viewData, tempData, writer);
var view = FindPartialView(tempContext, viewName);
var viewContext = new ViewContext(controllerContext, view, dictionary, tempData, writer);
view.Render(viewContext, writer);
return writer.ToString();
}
}
private static IView FindPartialView(ViewContext viewContext, string partialViewName)
{
var viewEngineCollection = ViewEngines.Engines;
ViewEngineResult result = viewEngineCollection.FindPartialView(viewContext, partialViewName);
if (result.View != null)
{
return result.View;
}
StringBuilder builder = new StringBuilder();
foreach (string str in result.SearchedLocations)
{
builder.AppendLine();
builder.Append(str);
}
throw new InvalidOperationException("Could not find view named:" + partialViewName + " The following locations where checked: " + builder);
}
Each time, like trying to send an email, I will not be sent, but it says:
An unhandled exception occurred while processing the request.
ArgumentNullException: Value cannot be null. Parameter name:
Templates/NewPassword does not match any available view
This is what it looks like when I refer to the file.
That's how I've tried to look here.Github - Paris Plyzos
Also code here:
var resultMail = await _viewRenderService.RenderToStringAsync("Templates/NewPassword", viewModel); //ERROR HERE!
var client = new SendGridClient(m.azureName());
var from = new EmailAddress(m.mailFrom(), m.nameFrom());
var to = new EmailAddress(mail, UserValue.Navn);
var plainTextContent = Regex.Replace(resultMail, "<[^>]*>", "");
var msg = MailHelper.CreateSingleEmail(from, to, title, plainTextContent: plainTextContent,
htmlContent: null);
var resulta = client.SendEmailAsync(msg);
return RedirectToAction("UserPassword");
RenderToStringAsync code here - I've written an error where the error goes wrong here.
public async Task<string> RenderToStringAsync(string viewName, object model)
{
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);//ERROR HERE
if (viewResult.View == null)
{
throw new ArgumentNullException($"{viewName} does not match any available view");
}
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();
}
}
How is your IViewRenderService.RenderToStringAsync() implementation finding views? If you are using IRazorViewEngine, the ViewName has to be fully qualified including file extension ex "~/Views/Home/Index.cshtml"
I need server side page render (without request) outside controller(I want to render view outside controller).
To do that, i need a fake HttpRequest to create ControllerContext so i can render view with razor.Render() and return
stringWriter.ToString() to client using signalr hub.
So i used this code to render partialview as string;
public class FakeController : Controller { }
HttpRequest httpRequest = new HttpRequest("", new UriBuilder("http", "localhost", 10654, "/" + "SomeController/SomeAction").Uri.ToString(), "");
var routeData = new RouteData();
routeData.Values.Add("Controller", "SomeController");
routeData.Values.Add("Action", "SomeAction");
StringWriter stringWriter = new StringWriter();
HttpResponse httpResponse = new HttpResponse(stringWriter);
HttpContext httpContextMock = new HttpContext(httpRequest, httpResponse);
var _contextWrapper = new HttpContextWrapper(httpContextMock);
var controllerContext = new ControllerContext(new RequestContext(_contextWrapper , routeData), new FakeController());
string filePath = "~/Areas/Site/Views/Shared/SomePartialView.cshtml";
var razor = new RazorView(controllerContext, filePath, null, false, null);
razor.Render(new ViewContext(controllerContext, razor, new ViewDataDictionary(model), new TempDataDictionary(), stringWriter), stringWriter);
return stringWriter.ToString();
Here when code tries to render #Html.HiddenFor(m => m.property)
I get error;
source : System.Web.WebPages
error : Object reference not set to an instance of an object
But the weird thing is that i can get #Model.property value(i mean it is not null) and also the other model properties(And it seems that only #Html.HiddenFor or #Html.Hidden is the problem).
I am trying to convert html string from my view to image. I am able to get my view as string but not able to convert it in image(base64string or byte array) to return it back. This is my code
/* Getting label design info */
LabelDesigner ObjModel = GetLabelDesignObject("Drug_Label");
/* Taking view's HTML */
string HTML = RenderRazorViewToString("~/Views/Folder/DrugLabel.cshtml", ObjModel);
.............
.............
.............
.............
public string RenderRazorViewToString(string ViewName, object Model)
{
string ReturnVal = string.Empty;
try
{
ViewData.Model = Model;
using (var sw = new System.IO.StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, ViewName);
var viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
return sw.GetStringBuilder().ToString();
}
}
catch (Exception ex)
{
/* Catch Exception Here */
log.Error("Error in Controller.cs, RenderRazorViewToString function: " + ex.Message);
}
return ReturnVal;
}
Here I am able to get HTML of view but how to convert that HTML string to jpg image?