Uploaded file gets lost when the model is passed inside RedirectToAction method - asp.net-mvc-4

This is the action method where I save user input and then redirect to the view mode:
[HttpPost]
public ActionResult SaveDocumentCitizen(DocumentCitizen documentCitizen)
{
DocumentCitizenRepository repository = new DocumentCitizenRepository();
repository.SaveDocument(documentCitizen);
return RedirectToAction(actionName: "ViewDocumentCitizen", routeValues: new RouteValueDictionary(documentCitizen));
}
And here's the ViewDocumentCitizen action method:
public ActionResult ViewDocumentCitizen(DocumentCitizen doc)// The Attachment value is null here
{
DocumentCitizenRepository repository = new DocumentCitizenRepository();
DocumentCitizen docCitizen = repository.UpdateTextualValues(doc.DocID);
return View(viewName: "DocumentCitizen", model: docCitizen);
}
The DocumentCitizen model has the following property:
public byte[] Attachment{get;set;}
I choose a file then submit the form and then when I debug the SaveDocumentCitizen method I can see that the Attachment is not null. But it gets set to null as soon as it's passed to the ViewDocumentCitizen method. What do I have to do to have the file property value persisted through redirection?

I think the problem is parameter name in RouteData and in Action method are not equal . Try to modify your SaveDocumentCitizen action
[HttpPost]
public ActionResult SaveDocumentCitizen(DocumentCitizen documentCitizen)
{
DocumentCitizenRepository repository = new DocumentCitizenRepository();
repository.SaveDocument(documentCitizen);
return RedirectToAction(actionName: "ViewDocumentCitizen", routeValues: new RouteValueDictionary(new RouteValueDictionary(new Dictionary<string, object> {{"doc", documentCitizen}})));
}
Or better
return RedirectToAction(actionName: "ViewDocumentCitizen", routeValues: new { doc = documentCitizen});
This should help to your ModelBinder to recognize parameter by name
EDIT:
From MSDN "Controller.RedirectToAction "
Returns an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action.
Get method has a length limit, read more HERE
But .Net Framework ready for everything, and they created ControllerBase.TempData property read more on MSDN
Hope it will help you

Related

Remote Validation in Asp.Net Core Razor Pages

I’m developing a web application using Razor Pages and Code First.
I know that in ASP.NET MVC, you can use Remote above a property referring to an action in a controller that validates the data without the whole page being posted back. But it doesn’t seem to work in Razor Pages as there’s no Controller and Action in ASP.NET Core Razor Pages.
So, How can I get remote validation done in Razor Pages?
For anyone like me who finds this later and loses their mind trying to pass a property from their model onto the validation method, make the method signature look like so
public IActionResult IsCharacterNameAvailable([Bind(Prefix = "Character.Name")] string name)
Character is the model and Name is the property. Without adding the [Bind(Prefix = "")] before the parameter I was always receiving a null value. Hope this helps!
I added the following in my model class:
[Remote(action: "IsNationalIdValid",controller:"Validations")]
I created 'Controllers' folder in my Razor Pages project and added a controller(ValidationsController) with the following method:
public IActionResult IsNationalIdValid(string nationalId){}
However,when I tried to go to the page where this validation was supposed to work,I got the following exception:
No URL for remote validation could be found in asp.net core
Thanks to a reply to the same thread in Asp.Net forum,I figured out the answer:
All I needed to do was to add the following code in Startup.cs file of my Razor Pages project in order to configure the route.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Hope this answer will help someone else as well.
In the base class that RemoteAttribute derives from there's a protected GetUrl() method than can be overriden. Therefore I created my own MyRemoteAttribute class
public class MyRemoteAttribute : RemoteAttribute
{
/// <summary>
/// Initialise an instance of the <see cref="MyRemoteAttribute"/>
/// </summary>
/// <param name="handler">The name of the Razor Page Handler</param>
/// <param name="page">The Razor Page name</param>
public MyRemoteAttribute(string handler = null, string page = null)
{
Handler = handler;
Page = page;
}
/// <summary>
/// Gets/sets the url to use for remote validation
/// </summary>
public string Url { get; set; }
public string Page { get; private set; }
public string Handler { get; private set; }
protected override string GetUrl(ClientModelValidationContext context)
{
// Use an URL is specified
if (!string.IsNullOrEmpty(Url)) return Url;
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (string.IsNullOrEmpty(Handler))
{
throw new InvalidOperationException("No Handler specified");
}
var services = context.ActionContext.HttpContext.RequestServices;
var factory = services.GetRequiredService<Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory>();
var urlHelper = factory.GetUrlHelper(context.ActionContext);
var page = Page?? context.ActionContext.RouteData.Values["page"] as string;
Url = urlHelper.Page(page, Handler);
if (Url == null)
{
throw new InvalidOperationException();
}
return Url;
}
}
In my app which is using areas, creates a url /BusinessPartners/Clients/Create?handler=CheckUnique
To use decorate your model property with [MyRemote(Url="/Something/Somecheck")] to use the specified URL, or [MyRemote("CheckUnique")] to specify the Razor Page Handler. The handler should be named OnGet<handlername> and needs to return a JsonResult with true for passing validation, false or null if validation fails.
The handler in the Razor Page is:
public IActionResult OnGetCheckUnique(string shortName)
{
var found = db.Queryable<BusinessPartner>().Any(a => a.ShortName == shortName);
return new JsonResult(!found);
}
This is the same as you would do for the RemoteAttribute with the exception on the slightly modified naming convention.
I like my validation to be close to the point where it is used so therefore I've put it in the same page. I also have use a single [BindProperty] for a model class just to keep things neat and manageable.
Looks like there is a feature request for remote validation in ASP.NET Core Razor Pages but it is not priority:
https://github.com/aspnet/Mvc/issues/8245
The PageRemoteValidation attribute was introduced in ASP.NET Core 3.0 and is designed specifically to work with a Razor Pages handler method.
So, if you are working with ASP.NET Core 2.x, or your validation endpoint is an MVC controller, you must use the RemoteValidation attribute. If you are working with ASP.NET Core 3.x or newer, AND your validation service is a Razor Pages handler method, you must use the PageRemoteValidation attribute.
Here is an example describing this in details:

Adding a WEB API method ruins my SWAGGER UI

This first method is fine. But when I add the second method the body of the SWAGGER UI is a bunch of html gibberish. And I creating the route the wrong way?
// GET api/checklist/1288
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var model = _checkListService.Get(id);
return Ok(model);
}
// http://localhost:64783/api/checklist/GetDelinquentItems?id=1288
[Route("GetDelinquentItems")]
public async Task<IActionResult> GetDelinquentItems(int id)
{
var model = _checkListService.GetDelinquentItems(id);
return Ok(model);
}
That 'html gibberish' (indeed not the most elegant way to show an error) still contains some useful information. The first line says:
500 internal server error
and in the last three lines you can read:
Ambiguos HTTP method for action...CheckListController.GetDelinquentItems... Actions require explicit HttpMethod binding for Swagger
therefore another
[HttpGet("{id}")]
before the GetDelinquentItems() method should solve the problem.

Redirecting to an Action Method from another Action Method in the same controller

I am a Newbie in asp.net and currently I am doing a web page application in MVC4 with Login functionality.
My Index action method looks like this-
public ActionResult Index()
{
var PageModelList1 = new DataAccessLayer.DataAccess().GetPageInfo();
ViewData["MenuList"] = PageModelList1.PageModelList;
return View();
}
and my LogIn action method looks like-
[HttpPost]
public ActionResult LogIn(LogInModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var PageModelList1 = new DataAccessLayer.DataAccess().GetPageInfo(model.UserName,model.Password);
ViewData["MenuList"] = PageModelList1.PageModelList;
return RedirectToAction("Index", "MyController");
}
ModelState.AddModelError("", "login failed");
return PartialView("_LogIn", model);
}
what I need is, when I Login successfully, the RedirectToAction("Index", "Deimos") should take place but the 'MenuList' there should be the new 'MenuList' from LogIn action method. How could I do it?
RedirectToAction will send a 302 response to the browser with the new url as the location header value and browser will make a totally new request to go to that page. This new request has no idea what you did in the previous request. So ViewData will not work. You may consider using TempData.
But TempData's life is only until the next request. After that it is gone. So if you want something on all the subsequent requests(like a menu to be shown to user), I suggest you read it from a database table every time you load the page. You can store the items to a cache after the first read to avoid constant hit(s) to the database if you are worried about that.
Another option is to set the menu items to Session variables and read from there. I am not a big fan of setting stuff like that to session. I prefer to read it from a cache (in which data was loaded from a db call) or so.

Validation messages from custom model validation attributes are locked to first loaded language

I am working on a multi lingual website using Umbraco 7.2.4 (.NET MVC 4.5). I have pages for each language nested under home nodes with their own culture:
Home (language selection)
nl-BE
some page
some other page
my form page
fr-BE
some page
some other page
my form page
The form model is decorated with validation attributes that I needed to translate for each language. I found a Github project, Umbraco Validation Attributes that extends decoration attributes to retrieve validation messages from Umbraco dictionary items. It works fine for page content but not validation messages.
The issue
land on nl-BE/form
field labels are shown in dutch (nl-BE)
submit invalid form
validation messages are shown in dutch (nl-BE culture)
browse to fr-BE/form
field labels are shown in french (fr-BE)
submit invalid form
Expected behavior is: validation messages are shown in french (fr-BE culture)
Actual behavior is: messages are still shown in dutch (data-val-required attribute is in dutch in the source of the page)
Investigation to date
This is not a browser cache issue, it is reproducible across separate browsers, even separate computers: whoever is generating the form for the first time will lock the validation message culture. The only way to change the language of the validation messages is to recycle the Application Pool.
I doubt that the Umbraco Validation helper class is the issue here but I'm out of ideas, so any insight is appreciated.
Source code
Model
public class MyFormViewModel : RenderModel
{
public class PersonalDetails
{
[UmbracoDisplayName("FORMS_FIRST_NAME")]
[UmbracoRequired("FORMS_FIELD_REQUIRED_ERROR")]
public String FirstName { get; set; }
}
}
View
#inherits Umbraco.Web.Mvc.UmbracoTemplatePage
var model = new MyFormViewModel();
using (Html.BeginUmbracoForm<MyFormController>("SubmitMyForm", null, new {id = "my-form"}))
{
<h3>#LanguageHelper.GetDictionaryItem("FORMS_HEADER_PERSONAL_DETAILS")</h3>
<div class="field-wrapper">
#Html.LabelFor(m => model.PersonalDetails.FirstName)
<div class="input-wrapper">
#Html.TextBoxFor(m => model.PersonalDetails.FirstName)
#Html.ValidationMessageFor(m => model.PersonalDetails.FirstName)
</div>
</div>
note: I have used the native MVC Html.BeginForm method as well, same results.
Controller
public ActionResult SubmitFranchiseApplication(FranchiseFormViewModel viewModel)
{
if (!ModelState.IsValid)
{
TempData["Message"] = LanguageHelper.GetDictionaryItem("FORMS_VALIDATION_FAILED_MESSAGE");
foreach (ModelState modelState in ViewData.ModelState.Values)
{
foreach (ModelError error in modelState.Errors)
{
TempData["Message"] += "<br/>" + error.ErrorMessage;
}
}
return RedirectToCurrentUmbracoPage();
}
}
LanguageHelper
public class LanguageHelper
{
public static string CurrentCulture
{
get
{
return UmbracoContext.Current.PublishedContentRequest.Culture.ToString();
// I also tried using the thread culture
return System.Threading.Thread.CurrentThread.CurrentCulture.ToString();
}
}
public static string GetDictionaryItem(string key)
{
var value = library.GetDictionaryItem(key);
return string.IsNullOrEmpty(value) ? key : value;
}
}
So I finally found a workaround. In attempt to reduce my app to its simplest form and debug it, I ended up recreating the "UmbracoRequired" decoration attribute. The issue appeared when ErrorMessage was set in the Constructor rather than in the GetValidationRules method. It seems that MVC is caching the result of the constructor rather than invoking it again every time the form is loaded. Adding a dynamic property to the UmbracoRequired class for ErrorMessage also works.
Here's how my custom class looks like in the end.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter,
AllowMultiple = false)]
internal class LocalisedRequiredAttribute : RequiredAttribute, IClientValidatable
{
private string _dictionaryKey;
public LocalisedRequiredAttribute(string dictionaryKey)
{
_dictionaryKey = dictionaryKey;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata, ControllerContext context)
{
ErrorMessage = LanguageHelper.GetDictionaryItem(_dictionaryKey); // this needs to be set here in order to refresh the translation every time
yield return new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage, // if you invoke the LanguageHelper here, the result gets cached and you're locked to the current language
ValidationType = "required"
};
}
}

RazorEngine Error trying to send email

I have an MVC 4 application that sends out multiple emails. For example, I have an email template for submitting an order, a template for cancelling an order, etc...
I have an Email Service with multiple methods. My controller calls the Send method which looks like this:
public virtual void Send(List<string> recipients, string subject, string template, object data)
{
...
string html = GetContent(template, data);
...
}
The Send method calls GetContent, which is the method causing the problem:
private string GetContent(string template, object data)
{
string path = Path.Combine(BaseTemplatePath, string.Format("{0}{1}", template, ".html.cshtml"));
string content = File.ReadAllText(path);
return Engine.Razor.RunCompile(content, "htmlTemplate", null, data);
}
I am receiving the error:
The same key was already used for another template!
In my GetContent method should I add a new parameter for the TemplateKey and use that variable instead of always using htmlTemplate? Then the new order email template could have newOrderKey and CancelOrderKey for the email template being used to cancel an order?
Explanation
This happens because you use the same template key ("htmlTemplate") for multiple different templates.
Note that the way you currently have implemented GetContent you will run into multiple problems:
Even if you use a unique key, for example the template variable, you will trigger the exception when the templates are edited on disk.
Performance: You are reading the template file every time even when the template is already cached.
Solution:
Implement the ITemplateManager interface to manage your templates:
public class MyTemplateManager : ITemplateManager
{
private readonly string baseTemplatePath;
public MyTemplateManager(string basePath) {
baseTemplatePath = basePath;
}
public ITemplateSource Resolve(ITemplateKey key)
{
string template = key.Name;
string path = Path.Combine(baseTemplatePath, string.Format("{0}{1}", template, ".html.cshtml"));
string content = File.ReadAllText(path);
return new LoadedTemplateSource(content, path);
}
public ITemplateKey GetKey(string name, ResolveType resolveType, ITemplateKey context)
{
return new NameOnlyTemplateKey(name, resolveType, context);
}
public void AddDynamic(ITemplateKey key, ITemplateSource source)
{
throw new NotImplementedException("dynamic templates are not supported!");
}
}
Setup on startup:
var config = new TemplateServiceConfiguration();
config.Debug = true;
config.TemplateManager = new MyTemplateManager(BaseTemplatePath);
Engine.Razor = RazorEngineService.Create(config);
And use it:
// You don't really need this method anymore.
private string GetContent(string template, object data)
{
return Engine.Razor.RunCompile(template, null, data);
}
RazorEngine will now fix all the problems mentioned above internally. Notice how it is perfectly fine to use the name of the template as key, if in your scenario the name is all you need to identify a template (otherwise you cannot use NameOnlyTemplateKey and need to provide your own implementation).
Hope this helps.
(Disclaimer: Contributor of RazorEngine)