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).
Related
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);
}
I have two controllers, 1 is an api controller, the other is a view controller, what I am trying to do is generate the PDF from the api controller from a view in the view controller...is this possible and how would I do it?
API Controller - This is where I want to generate the PDF
public byte[] getReportsPDF(string community, string procedure)
{
byte[] pdfBytes = new byte[] { };
System.Web.Mvc.ControllerContext context = new System.Web.Mvc.ControllerContext();
if(procedure == "GetProductionTasks")
{
var actionPDF = new Rotativa.ActionAsPdf("RedBluePDF", new { community = community, procedure = procedure })
{
PageSize = Size.A4,
PageOrientation = Rotativa.Options.Orientation.Landscape,
PageMargins = { Left = 1, Right = 1 }
};
pdfBytes = actionPDF.BuildFile(context);
}
return pdfBytes;
}
View Controller - This is the view I want to generate.
public ActionResult RedBluePDF(string community, string procedure)
{
return View();
}
After trying for a very long time, finally I managed to solve by this:
var controller = DependencyResolver.Current.GetService<ViewController>();
RouteData route = new RouteData();
route.Values.Add("action", "RedBluePDF");
route.Values.Add("controller", "ApiController");
ControllerContext newContext = new ControllerContext(new HttpContextWrapper(System.Web.HttpContext.Current), route, controller);
controller.ControllerContext = newContext;
controller.RedBluePDF(community, procedure);
I have been trying:
var controller = DependencyResolver.Current.GetService<ControllerB>();
controller.ControllerContext = new ControllerContext(this.Request.RequestContext, controller);
But this will give me the wrong pdf, because it will use your current controller (API Controller in this case) to make the call for ActionAsPdf()
Credit to this answer
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 have a working application for which I need to now add a full set of unit tests. The current code stores the user information as follows:
HttpContext.Current.Session["UserInfo"] = userData;
I'm using moq for my testing, and my unit test has the following code:
var server = new Mock<HttpServerUtilityBase>(MockBehavior.Loose);
var response = new Mock<HttpResponseBase>(MockBehavior.Strict);
var request = new Mock<HttpRequestBase>(MockBehavior.Strict);
var context = new Mock<HttpContextBase>();
context.SetupGet(x => x.Request).Returns(request.Object);
context.SetupGet(x => x.Response).Returns(response.Object);
context.SetupGet(x => x.Server).Returns(server.Object);
var controller = new LoginController();
controller.ControllerContext = new ControllerContext(
context.Object,
new RouteData(),
controller);
JsonResult result = controller.LoginUser(
new LoginHelper {
userName = "myusername",
password = "invalidpassword"
}
) as JsonResult;
Of course when I get to where the login process tries to create the session data, I get an "Object reference not set to an instance of an object." error becuase HttpContext.Current is null.
Some research has shown me that using Current isn't exactly compatible with MSTest, so I understand that I might need to change the way I store/load my user information. However, I would like some advice on which way to go here.
I would appreciate any suggestions on either how to get my unit tests to work, or a different method to store the user info to make it more compatible with unit tests.
This was the solution I came up with. It is a combination of a few things I found in various places including here:
First, I created 2 classes .. MockHttpSession, and MockHelpers
public class MockHttpSession : HttpSessionStateBase
{
Dictionary<string, object> m_SessionStorage = new Dictionary<string, object>();
public override object this[string name]
{
get { return m_SessionStorage[name]; }
set { m_SessionStorage[name] = value; }
}
public override void Abandon()
{
// Do nothing
}
}
public class MockHelpers
{
public static HttpContext FakeHttpContext()
{
var httpRequest = new HttpRequest("", "http://localhost/", "");
var stringWriter = new StringWriter();
var httpResponse = new HttpResponse(stringWriter);
var httpContext = new HttpContext(httpRequest, httpResponse);
var sessionContainer = new HttpSessionStateContainer(
"id",
new SessionStateItemCollection(),
new HttpStaticObjectsCollection(),
10,
true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc,
false);
SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);
return httpContext;
}
}
Then I changed my unit test code to use these:
var server = new Mock<HttpServerUtilityBase>(MockBehavior.Loose);
var response = new Mock<HttpResponseBase>(MockBehavior.Strict);
var request = new Mock<HttpRequestBase>(MockBehavior.Strict);
var session = new MockHttpSession();
var context = new Mock<HttpContextBase>();
context.SetupGet(x => x.Request).Returns(request.Object);
context.SetupGet(x => x.Response).Returns(response.Object);
context.SetupGet(x => x.Server).Returns(server.Object);
context.SetupGet(x => x.Session).Returns(session);
HttpContext.Current = MockHelpers.FakeHttpContext();
var controller = new LoginController();
controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
JsonResult result = controller.LoginUser(new LoginHelper { userName = "MyUserName", password = ""InvalidPassword }) as JsonResult;
This was able to successfully test the code with both valid and invalid passwords.
Thanks everyone for your help
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.