I am using the Bootstrap DateTime picker and formatting it to a UK format in a .NET 5 MVC project using a Razor page front end.
The input is as below
<input type="text" asp-for="WeddingDate" class="form-control" onblur="WizardSummary('WeddingDate','SummaryWeddingDate')" required />
And the javascript initialing it
$('.datetimepicker').datetimepicker({
icons: {
time: "fas fa-clock",
date: "fa fa-calendar",
up: "fa fa-chevron-up",
down: "fa fa-chevron-down",
previous: 'fa fa-chevron-left',
next: 'fa fa-chevron-right',
today: 'fa fa-screenshot',
clear: 'fa fa-trash',
close: 'fa fa-remove'
},
format: 'DD/MM/YYYY HH:mm:ss'
});
Fairly simple stuff. It all works absolute fine in the front end, it fires, formats the date etc... no issues at all.
However, when I POST the data to the controller for a date with a day > 12 (example todays date - 24/09/2021), it comes through as 0001/01/01 00:00:00 and I think this is because the model expects a standard DateTime, which would be US format and it translates it to this value. I can see in the chrome developer tools it is posting a value in UK format as expected.
Extract of model below
public class WeddingWizardViewModel
{
public DateTime WeddingDate { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
What have I gotten wrong? How can I get my backend to recognise I am posting a valid format?
Update Here is the WizardSummary function, which is purely to copy the content of the input box into a text label that I display on a sumarry screen.
function WizardSummary(input, output) {
var inputvalue = document.getElementById(input).value;
document.getElementById(output).innerHTML = inputvalue;
}
Controller method on submission of form*
[HttpPost]
public async Task<IActionResult> Wizard(WeddingWizardViewModel vm)
{
var user = _userManager.GetUserAsync(User).Result;
vm.TopBar = new TopBarViewModel();
vm.TopBar.PageTitle = "Wedding Wizard";
vm.TopBar.Breadcrumbs = new List<Breadcrumb>();
var level1 = new Breadcrumb
{
Level = 1,
Name = "Weddings",
Controller = "Weddings",
Action = "Index"
};
vm.TopBar.Breadcrumbs.Add(level1);
var level2 = new Breadcrumb
{
Level = 2,
Name = "Wizard",
Controller = "Weddings",
Action = "Wizard"
};
vm.TopBar.Breadcrumbs.Add(level2);
this.ViewData["TopBarViewModel"] = vm.TopBar;
var templatesegments = _context.TemplateSegments.Include(i => i.Shots.OrderBy(i => i.Id)).Where(i => i.TemplateId == vm.TemplateId).ToList() ;
List<Segment> segments = new List<Segment>();
foreach(var templatesegment in templatesegments)
{
Segment newseg = new Segment();
newseg.CompanyId = user.CompanyId;
newseg.Name = templatesegment.Name;
newseg.Created = DateTime.UtcNow;
newseg.Modified = DateTime.UtcNow;
newseg.StartTime = new DateTime(vm.WeddingDate.Year, vm.WeddingDate.Month, vm.WeddingDate.Day, vm.WeddingDate.Hour, vm.WeddingDate.Minute, vm.WeddingDate.Second);
newseg.ReminderInMinutes = 15;
newseg.ClientVisible = templatesegment.ClientVisible;
newseg.Shots = new List<Shot>();
foreach (var templateshot in templatesegment.Shots)
{
Shot newshot = new Shot();
newshot.CompanyId = user.CompanyId;
newshot.Created = DateTime.UtcNow;
newshot.Modified = DateTime.UtcNow;
newshot.Name = templateshot.Name;
newshot.ClientVisible = templateshot.ClientVisible;
newshot.Sequence = templateshot.Sequence;
newseg.Shots.Add(newshot);
}
segments.Add(newseg);
}
Wedding wedding = new Wedding();
wedding.Client1EMail = vm.Client1EMail;
wedding.Client1FirstName = vm.Client1FirstName;
wedding.Client1LastName = vm.Client1LastName;
wedding.Client1FullMobileNumber = vm.Client1FullMobileNumber;
wedding.Client1Role = vm.Client1Role;
wedding.Client2EMail = vm.Client2EMail;
wedding.Client2FirstName = vm.Client2FirstName;
wedding.Client2FullMobileNumber = vm.Client2FullMobileNumber;
wedding.Client2LastName = vm.Client2LastName;
wedding.Client2Role = vm.Client2Role;
wedding.CompanyId = user.CompanyId;
wedding.Description = vm.Description;
wedding.Name = vm.Name;
wedding.Segments = segments;
wedding.WeddingDate = vm.WeddingDate;
_context.Add(wedding);
_context.SaveChanges();
//Image Upload
if (vm.WeddingAvatar != null)
{
var upload = await FileUpload.Upload(vm.WeddingAvatar, _env, user, "weddingavatars", wedding.Id.ToString());
if (upload.Success == true)
{
wedding.WeddingAvatarFileName = upload.FileName;
}
else
{
return BadRequest("Error uploading file");
}
}
_context.Update(wedding);
_context.SaveChanges();
TempData["message"] = "Your wedding has been created and you can now start adding segments and shots to it";
return RedirectToAction("Edit", new { id = wedding.Id });
}
Please, try to set the locale property of your date time picker; in the code bellow sounds an example of UK locale:
$('.datetimepicker').datetimepicker({
icons: {
time: "fas fa-clock",
date: "fa fa-calendar",
up: "fa fa-chevron-up",
down: "fa fa-chevron-down",
previous: 'fa fa-chevron-left',
next: 'fa fa-chevron-right',
today: 'fa fa-screenshot',
clear: 'fa fa-trash',
close: 'fa fa-remove',
locale: 'uk'
},
format: 'DD/MM/YYYY HH:mm:ss'
});
And in Back-End, at Controller ActionResult method, add in first line this code bellow:
var cultureInfo = CultureInfo.GetCultureInfo("en-uk");
Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;
Related
For the possible duplicate, I already know how to preview my image before uploading it, my issue as detailed below is that when I submit the Form, the image is being received as null.
I am trying to submit a form to an MVC controller that should submit a model, a string, and an Image File,
I made sure that the input has the same name as the parameter within the controller
Following is the Form Initialization code
#using (Html.BeginRouteForm(Sitecore.Mvc.Configuration.MvcSettings.SitecoreRouteName,
new
{
calendar = System.Convert.ToString(Request.QueryString["calendar"]),
ID = System.Convert.ToString(Request.QueryString["id"])
},
FormMethod.Post, new { enctype = "multipart/form-data" }))
{
//Model Input
<input style="opacity: 0;" name="EventImage" type="file" accept="image/jpeg, image/jpg" id="ImageUpload" onchange="readURL(this);" />
}
And the Controller Header
[HttpPost]
[ValidateInput(false)]
public ActionResult AddEvent(Event model, string calendar, HttpPostedFileBase EventImage)
The EventImage Parameter is being returned null and I can't seem to figure out why.
I thought that the ID might be causing the problem so I changed the name attribute to ImageUpload as well as the Parameter within the controller but to no avail as the value is still null.
Additional Info: when a User uploads an image, I let them preview it in an Image box, could that be causing it?
Thanks,
Update:
Here is the code for the readURL function
function readURL(input) {
if (input.files && input.files[0]) {
var ImageCorrect = false;
var file = input.files[0];
var reader = new FileReader();
reader.onload = function (e) {
// Concatenate our HTML image info
var ext = file.name.match(/\.([^\.]+)$/)[1];
switch (ext) {
case 'jpg':
case 'jpeg':
case 'JPG':
case 'JPEG':
{
if ((Math.round(file.size / 1024)) > 500) {
alert('Image is too Large');
}
else {
var image = new Image();
image.src = e.target.result;
image.onload = function () {
var width = parseInt(image.width);
if (width <= 500) {
$('#previewImage').attr('src', e.target.result);
$('#previewImage').show();
}
else {
alert('Image width exceeds maximum width');
}
}
}
}
break;
default:
alert('Image type not allowed')
}
}
reader.readAsDataURL(input.files[0]);
}
}
If I understood your question right you are trying to submit a file from your form to your controller and you get null in the controller.
I did this before, check the following:
cshtml (you can add your attributes to event image like JS call...etc ):
<div class="form-group">
#Sitecore.Globalization.Translate.Text("EventImage")<br>
#Html.TextBoxFor(m => m.EventImage, new { type = "file" })
</div>
Model:
[Display(Name = "Event Image")]
public HttpPostedFileBase EventImage { get; set; }
Controller Signature:
[HttpPost]
[ValidateInput(false)]
public ActionResult AddEvent(Event model)
Catching the Image field:
if (model.EventImage != null && model.EventImage.ContentLength > 0)
{
var fileName = Path.GetFileName(model.EventImage.FileName);
var tempPath = Server.MapPath("~/Temp/uploads");
var path = Path.Combine(tempPath, fileName);
if (!Directory.Exists(tempPath))
Directory.CreateDirectory(tempPath);
model.EventImage.SaveAs(path);
Sitecore.Resources.Media.MediaCreatorOptions options = new Sitecore.Resources.Media.MediaCreatorOptions();
options.FileBased = false;
options.IncludeExtensionInItemName = false;
options.KeepExisting = false;
options.Versioned = false;
options.Destination = "/sitecore/media library/Images/" + ItemUtil.ProposeValidItemName(Path.GetFileName(path));
options.Database = Sitecore.Configuration.Factory.GetDatabase(MasterDatabase);
// Now create the file
Sitecore.Resources.Media.MediaCreator creator = new Sitecore.Resources.Media.MediaCreator();
MediaItem mediaItem = creator.CreateFromFile(path, options);
ImageField _eventImage = (ImageField)_event.Fields[EventImage];
_eventImage.MediaID = mediaItem.ID;
PublishItem(mediaItem);
}
I have ASP.NET MVC Project and I have some module. Some modules have pagination. For test and understand MvcSiteMapProvider I working with one module Forum and created ForumDynamicNodeProvider class
public class ForumDynamicNodeProvider : DynamicNodeProviderBase
{
private readonly IForumsService _forumsService;
public ForumDynamicNodeProvider(IForumsService forumsService)
{
this._forumsService = forumsService;
}
public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
{
string rootTitle = ManagerLocalization.Get("Forums", "FORUMS");
var nodes = new List<DynamicNode>
{
new DynamicNode
{
Title = rootTitle,
Controller = "Forums",
Action = "Index",
Key = "forum_home"
}
};
var forums = this._forumsService.GetForums<ForumNode>().ToList();
var topics = this._forumsService.GetTopics<TopicNode>().ToList();
foreach (var forum in forums)
{
var parentForum = this.GetParentForum(forums, forum);
string parentKey = parentForum?.Id.ToString() ?? "home";
var forumRouteValue = new Dictionary<string, object> { { "forumName", forum.NameTranslit } };
nodes.Add(new DynamicNode
{
Key = $"forum_{forum.Id}",
ParentKey = $"forum_{parentKey}",
Title = forum.Name,
Controller = "Forums",
Action = "ShowForum",
RouteValues = forumRouteValue
});
}
foreach (var topic in topics)
{
var forum = forums.FirstOrDefault(item => item.Id == topic.ForumId);
var forumRouteValue = new Dictionary<string, object> { { "forum", forum.NameTranslit }, { "topicName", topic.TitleTranslite }, {"page", 0 } };
nodes.Add(new DynamicNode
{
Key = $"topic_{topic.Id}",
ParentKey = $"forum_{topic.ForumId}",
Title = topic.Title,
Controller = "Forums",
Action = "ShowTopic",
RouteValues = forumRouteValue
});
}
return nodes;
}
private ForumNode GetParentForum(List<ForumNode> forums, ForumNode forum)
{
if (forum.ForumId > 0)
{
return forums.FirstOrDefault(item => item.Id == forum.ForumId);
}
return null;
}
}
But I can't found a good decision for pagination. For easy I can use page prefix for key and make duplicate DynamicNode. But it's bad idea, because when I have example 1000 topics and each topic have 20 page I must create 20000 DynamicNode. Maybe have other decision?
For ambient context (such as page number) you can use PreservedRouteParameters to force a match on any value for the specified keys. These keys match either route values or query string parameters from the request (route values take precedence if they are the same).
foreach (var forum in forums)
{
var parentForum = this.GetParentForum(forums, forum);
string parentKey = parentForum?.Id.ToString() ?? "home";
var forumRouteValue = new Dictionary<string, object> { { "forumName", forum.NameTranslit } };
// Always match the "page" route value regardless of its value
var forumPreservedRouteParameters = new List<string>() { "page" };
nodes.Add(new DynamicNode
{
Key = $"forum_{forum.Id}",
ParentKey = $"forum_{parentKey}",
Title = forum.Name,
Controller = "Forums",
Action = "ShowForum",
RouteValues = forumRouteValue,
PreservedRouteParameters = forumPreservedRouteParameters
});
}
NOTE: When you use PreservedRouteParameters, they are included in the generated URL from the current request if provided and not included in the URL if not provided in the request. Therefore, if you have more than one page number in the same ancestry you need to have a separate route key name for each one or the current page number will be passed to the ancestor nodes from the current request.
I have three dropdown list:
#Html.DropDownListFor(m => m.Edit.SelectCountryId, Model.Edit.Countries, "Please select", new { id = "CountryID", #class = "form-control", #onchange = "LoadRegions();" })
#Html.DropDownListFor(m => m.Edit.SelectRegionId,Model.Edit.Region, "Please select", new { id = "RegionID", #class = "form-control", #onchange = "LoadCities();" })
#Html.DropDownListFor(m => m.Edit.SelectCityId, Model.Edit.City, "Please select", new { id = "CityID", #class = "form-control" })
I populate second one depending on first one and third depending on second...but when i want to display those dropdown lists for some specific user im able to display just countries
My model:
public int SelectCountryId { get; set; }
public int SelectRegionId { get; set; }
public int SelectCityId {get; set;}
public System.Web.Mvc.SelectList City { get; set; }
public System.Web.Mvc.SelectList Region{ get; set; }
public System.Web.Mvc.SelectList Country{ get; set; }
My controller:
model.Edit.SelectCountryId = user.ContactInformation.First().City.Region.Country.Id;
model.Edit.SelectRegionId = user.ContactInformation.First().City.Region.Id;
model.Edit.SelectCityId = user.ContactInformation.First().City.Id;
I get values on controller but i cant get display them in view
EDIT:
My scripts:
var region = $('#RegionID');
var urlCountries = "/Account/GetRegions";
$.getJSON(urlCountries, { countryId: $('#CountryID').val() }, function (response) {
// clear and add default (null) option
region.empty().append($('<option></option>').val('').text('Please select'));
for (var i = 0; i < response.length; i++) {
region.append($('<option></option>').val(response[i].Id).text(response[i].Name));
}
});
var city = $('#CityID');
var url = "/Account/GetCities"; // use the helper (dont hard code)
$.getJSON(url, { regionId: $('#RegionID').val() }, function (response) {
// clear and add default (null) option
city.empty().append($('<option></option>').val('').text('Please select'));
for (var i = 0; i < response.length; i++) {
city.append($('<option></option>').val(response[i].Id).text(response[i].Name));
}
});
My methods for GetCity() and GetRegions()
public IEnumerable<IRegion> GetRegions(int countryId)
{
Model.Administration.Region[] modelRegions = Model.Administration.Region.FindAll(DetachedCriteria.For<Model.Administration.Region>().Add(Restrictions.Eq("Country.Id", countryId)));
return modelRegions.Select(Region.ConvertFromModel).Cast<IRegion>().ToList();
}
public IEnumerable<ICity> GetCities(int regionId)
{
CityLimited[] modelCities = CityLimited.FindAll(DetachedCriteria.For<CityLimited>().Add(Restrictions.Eq("Region.Id", regionId)));
return modelCities.Select(City.ConvertFromModel).Cast<ICity>().ToList();
}
You need to pass an additional value indicating the selected option in the json data your returning to the view. Your GetRegions() method should look like
public JsonResult GetRegions(int countryId) // return a JsonResult
{
Model.Administration.Region[] modelRegions = Model.Administration.Region.FindAll(DetachedCriteria.For<Model.Administration.Region>().Add(Restrictions.Eq("Country.Id", countryId)));
var options = modelRegions.Select(Region.ConvertFromModel).Cast<IRegion>().ToList();
var selection = 1; // set the value of the selected option here - must match the `ID` property of one of one of the `IRegion` your returning
return Json(new { options = options, selection = selection }, JsonRequestBehavior.AllowGet);
}
Side note: Its not clear what modelRegions.Select(Region.ConvertFromModel).Cast<IRegion>().ToList(); returns but if IRegion contains more that just the ID and Name properties you need for the view, consider creating a collection of anonymous objects so you don't send unnecessary data across the wire
Then the corresponding script should be
var url = '#Url.Action("GetRegions", "Account")'; // use the helper (dont hard code)
var regions = $('#RegionID'); // cache the element
$('#CountryID').change(function() {
$.getJSON(url, { id: $(this).val() }, function(response) {
// clear and add default (null) option
regions.empty().append($('<option></option>').val('').text('Please select'));
$.each(response.options, function(index, item) {
cities.append($('<option></option>').val(item.Value).text(item.Text));
});
regions.val(response.selection); // set the selected value
});
});
and remove the #onchange = "LoadRegions(); from you markup
Ditto for the GetCities() controller method and the corresponding script
I am using this code in my view:
#(Html.Telerik().TreeView()
.Name("AjaxTreeView")
.BindTo(Model, (item, category) =>
{
// bind initial data - can be omitted if there is none
item.Text = category.Name;
item.Action("Details", "Categories", new { Id = category.Id });
item.Value = category.Id.ToString();
item.LoadOnDemand = category.NOChildren > 0;
})
.DataBinding(dataBinding => dataBinding
.Ajax().Select("_TreeViewAjaxLoading", "Categories")
)
)
It works fine (ajaxified expand and collapse). The action links work fine but only for the root nodes. My current controller that spews out JSON for the ajax load:
[Transaction]
[HttpPost]
public ActionResult _TreeViewAjaxLoading(TreeViewItem node)
{
int? ParentId = !string.IsNullOrEmpty(node.Value) ? (int?)Convert.ToInt32(node.Value) : null;
var nodes = from item in CategoryRepository.GetChildren(ParentId)
select new TreeViewItem
{
Text = item.Name,
Value = item.Id.ToString(),
LoadOnDemand = item.NOChildren > 0
};
return new JsonResult { Data = nodes };
}
does not set the action link. How can I set the action link here? Thanks.
Christian
This seems to do the trick:
[Transaction]
[HttpPost]
public ActionResult _TreeViewAjaxLoading(TreeViewItem node)
{
int? ParentId = !string.IsNullOrEmpty(node.Value) ? (int?)Convert.ToInt32(node.Value) : null;
UrlHelper u = new UrlHelper(this.ControllerContext.RequestContext);
var nodes = from item in CategoryRepository.GetChildren(ParentId)
select new TreeViewItem
{
Text = item.Name,
Value = item.Id.ToString(),
LoadOnDemand = item.NOChildren > 0,
Url = u.Action("Details", "Categories", new { Id = item.Id} )
};
return new JsonResult { Data = nodes };
}
Can anyone help me? I'm doing some tests in ASP.NET MVC.
I want to implement the nice URL as stackoverflow.com routing system.
For example:
stackoverflow.com/questions/1074/asp-mvc
domain.com/id/title
Here is my code:
in the Global.asax
routes.MapRoute(null,
"posts/{PostId}/{Title}",
new { controller = "Posts", action = "Details", post = (string)null },
new { PostId = #"\d+", Title = #"([A-Za-z0-9-]+)" }
);
in the View:
<%= Html.ActionLink(Model.Title, "Details", new { Model.PostID, Model.Title})%>
With those codes, I am geting the url : http://localhost:53171/posts/Details?PostID=5&Title=Test-title
Can one one advise me?
Thank you very much.
Not sure what all of the stackoverflow url means, but if you wanted a clean url like:
https://stackoverflow.com/Questions/132/thetitle
In your Global.asax:
routes.MapRoute("PostController",
"Questions/{post_id}/{post_title}",
new { controller = "Post", action = "Index" },
new { post_id = #"\d+", post_title = #"([A-Za-z0-9-]+)" }
);
In your controller retrieve the values:
public ActionResult Index(Int32 post_Id, String post_title)
{
return View();
}
To produce the correct url use Html.RouteLink:
<%= Html.RouteLink("The Title", "PostController", new { post_id = 132, post_title = "thetitle" })%>
I think you need to put some default values into your route for the post title... and make sure they map through. You don't seem to have default values for 'postId' and 'title' and yet you have one for a non-existant 'post' route value.
routes.MapRoute(
"PostDetails",
"posts/{postId}/{title}",
new { controller = "Posts", action = "Details", postId = 0, title = "" },
new { PostId = #"\d+", Title = #"([A-Za-z0-9-]+)" }
);
Posts controller
public ActionResult Details(int postId, string title)
{
//whatever
}
Then in your view
<%= Html.ActionLink(Model.Title, "Details", new { #postId = Model.PostID, #title = Model.Title }) %>
Or
<%= Html.ActionLink(Model.Title, "Details", "Posts", new { #postId = Model.PostID, #title = Model.Title }, null) %>
I would also suggest creating a TitleSlug property on your posts model.
E.g. (code taken from here)
public partial class Post
{
public string TitleSlug
{
get
{
string str = Title.ToLower();
str = Regex.Replace(str, #"[^a-z0-9\s-]", ""); // invalid chars
str = Regex.Replace(str, #"\s+", " ").Trim(); // convert multiple spaces into one space
str = str.Substring(0, str.Length <= 45 ? str.Length : 45).Trim(); // cut and trim it
str = Regex.Replace(str, #"\s", "-"); // hyphens
return str;
}
}
}
HTHs
Charles
You are on the right path but you just need to match the name of your Controller ActionMethod parameters to the object route values in your Html.ActionLink.
public ActionResult Index(int PostId, string Title)
{
...
return View();
}
<%= Html.ActionLink(Model.Title, "Details",
new { PostId = Model.PostID, Title = Model.Title}) %>