Nested objects in view model confuses MVC 4 routing? - asp.net-mvc-4

So I have this view and it's generated by CMS. The brains of the view are in another view rendered by RenderAction.
Up until now it has looked like this:
Main.cshtml
<div>CMS content</div>
#{
var myContent = new MainContentModel() {
SomethingUsedFurtherDown = "Easier for CMS people to edit"
}
Html.RenderAction("_Main", "Foo", new { arg1 = "this", arg2 = "that", content = myContent });
}
MainContentModel.cs
namespace MyApp.Services.ViewModels.Foo
{
public class MainContentModel
{
public string SomethingUsedFurtherDown { get; set; }
}
}
MainViewModel.cs
namespace MyApp.Services.ViewModels.Foo
{
public class MainViewModel
{
public string Arg1 { set; set; }
public string Arg2 { set; set; }
public string Name { get; set; }
public string Age { get; set; }
public string Address { get; set; }
public MainContentModel Content { get; set; }
}
}
_Main.cshtml
#model MyApp.Services.ViewModels.Foo.MainViewModel
#Html.EditorFor(m => m.Name)
#Html.EditorFor(m => m.Age)
#Html.EditorFor(m => m.Address)
<!-- whatever - doesn't matter -->
FooController.cs
namespace My.App.Controllers
{
using System;
using System.Web.Mvc;
public class FooController
{
public ActionResult Main(string arg1, string arg2, MainContentModel cm)
{
var vm = new MainViewModel() { Arg1 = arg1, Arg2 = arg2, Content = cm };
return this.View("_Main", vm);
}
}
}
All works fine. So why am I bothering you? Well, this isn't the real code, obviously. There are many more arguments to the controller method and it's all getting rather messy. So I figured I would pass in the view model from the outer view.
Main.cshtml
<div>CMS content</div>
#{
var myVM = new MainViewModel() {
Arg1 = "this"
, Arg2 = "that"
, Content = new MainContentModel() {
SomethingUsedFurtherDown = "Easier for CMS people to edit"
}
};
Html.RenderAction("_Main", "Foo", new { vm = myVM });
}
...and just have one argument to the controller method
FooController.cs
namespace My.App.Controllers
{
using System;
using System.Web.Mvc;
public class FooController
{
public ActionResult Main(MainViewModel vm)
{
return this.View("_Main", vm);
}
}
}
And that's where the trouble starts. I get this error:
The view '_Main' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Foo/_Main.aspx
~/Views/Foo/_Main.ascx
~/Views/Shared/_Main.aspx
~/Views/Shared/_Main.ascx
~/Views/Foo/_Main.cshtml
~/Views/Foo/_Main.vbhtml
~/Views/Shared/_Main.cshtml
~/Views/Shared/_Main.vbhtml
~/Views/CMS/_Main.cshtml
~/Views/CMS/Foo/_Main.cshtml
But if I remove the initialisation of the content model, it all works again. Well, okay, it doesn't work work. But the error goes away, leaving me to come up with the obvious solution of adding the content model to the argument list and assigning it to the view model in the controller method.
Main.cshtml
<div>CMS content</div>
#{
var myVM = new MainViewModel() {
Arg1 = "this"
, Arg2 = "that"
};
var myCm = new MainContentModel() {
SomethingUsedFurtherDown = "Easier for CMS people to edit"
};
Html.RenderAction("_Main", "Foo", new { vm = myVM, cm = myCm });
}
FooController.cs
namespace My.App.Controllers
{
using System;
using System.Web.Mvc;
public class FooController
{
public ActionResult Main(MainViewModel vm, MainContentModel cm)
{
vm.Content = cm;
return this.View("_Main", vm);
}
}
}
Which is fine except that there are quite a few child objects of the real view model and I don't want to separate those out into arguments - it defeats the purpose of what is supposed to be a tidying up exercise.
Question
Is MVC4 supposed to be binding the child objects seamlessly and this is a bug? Or does it just not work this way and my only choice is to separate them out into extra parameters as above?
Cheers,
.pd.

Just in case anybody else has fallen into this trap. The problem was not the model binding. MVC 4 binds nested objects just fine. I was missing the subtlety of the error. The standard here is to prefix the "real" view name with an underscore and it was this that was not being found. The failure was occurring here:
return this.View("_Main");
What I didn't include in my post (cuz if I'd remembered, that would've fixed it!) was the fact that in the real situation the RenderAction looks like this:
RenderAction("Main", "Foo", new { area = "SomeArea", ... });
And I had missed off the area in my refactoring. Pretty embarrassing but if it stops somebody else beating their head against the wall then the shame will have been worth it :-)

Related

Blazor sanitize MarkupString

I'm trying to sanitize content of MarkupString. Actually I created this (based from https://github.com/dotnet/aspnetcore/blob/574be0d22c1678ed5f6db990aec78b4db587b267/src/Components/Components/src/MarkupString.cs)
public struct MarkupStringSanitized
{
public MarkupStringSanitized(string value)
{
Value = value.Sanitize();
}
public string Value { get; }
public static explicit operator MarkupStringSanitized(string value) => new MarkupStringSanitized(value);
public override string ToString() => Value ?? string.Empty;
}
But render output isn't raw html. How should I implement MarkupStringSanitized to use
#((MarkupStringSanitized)"Sanitize this content")
Couple of suggestions (Not necessarily for OP, but for anyone else looking to solve the problem):
You didn't provide the code that does the actual sanitization, so I'm going to state the (hopefully) obvious best practice and if you're following it, great. Do not use Regular Expressions (Regex) to parse HTML
Also, the Sanitize() method should follow the pattern of immutability in this case
I would suggest the following library Gans.XSS.HtmlSanitizer which is an active library and updated regularly.
The problem
Razor View Engine can doesn't know how to render a MarkupStringSanitized. Just because you duck typed a sanitized version of the same struct doesn't mean it can render it. To get this to render, you'll need to cast it to something it does know, MarkupString
Here's what happens when I used your HtmlSanitizedMarkup directly with no modifications.
#((MarkupStringSanitized)Content)
Working Example #1
Here's an example using my Markdown -> Html playground (fully tested and working):
MarkupStringSanitized.cs
public struct MarkupStringSanitized
{
public MarkupStringSanitized(string value)
{
Value = Sanitize(value);
}
public string Value { get; }
public static explicit operator MarkupStringSanitized(string value) => new MarkupStringSanitized(value);
public static explicit operator MarkupString(MarkupStringSanitized value) => new MarkupString(value.Value);
public override string ToString() => Value ?? string.Empty;
private static string Sanitize(string value) {
var sanitizer = new HtmlSanitizer();
return sanitizer.Sanitize(value);
}
}
MarkupStringSanitizedComponent.razor
#if (Content == null)
{
<span>Loading...</span>
}
else
{
#((MarkupString)(MarkupStringSanitized)Content)
}
#code {
[Parameter] public string Content { get; set; }
}
That extra conversion though is ugly IMO. (maybe someone smarter than me can clean that up?)
Working example #2
Here I tried extending the MarkupString with an extension method. It looks a little better, but only a little.
MarkupStringExtensions.cs
public static class MarkupStringExtensions
{
public static MarkupString Sanitize(this MarkupString markupString)
{
return new MarkupString(SanitizeInput(markupString.Value));
}
private static string SanitizeInput(string value)
{
var sanitizer = new HtmlSanitizer();
return sanitizer.Sanitize(value);
}
}
MarkupStringSanitizedComponent.razor
#if (Content == null)
{
<span>Loading...</span>
}
else
{
#(((MarkupString)Content).Sanitize())
}
#code {
[Parameter] public string Content { get; set; }
}

Getting model to viewmodel easily

I have a view-model like this:
public class U1MyProfile1ViewModel : U1Profile
{
public List<SelectListItem> CountryList { get; set; }
}
Thinking that I want the model accessible to the view, plus a some extra fields that aren't really part of the model, such as a drop down list of countries.
Then in the controller I try to "pass the model over to the view-model"
var myProfile = await _mainDbContext.U1Profiles
.AsNoTracking()
.FirstOrDefaultAsync(i => i.SiteUserId == mySiteUserId);
U1MyProfile1ViewModel myProfileViewModel = (U1MyProfile1ViewModel)myProfile;
this compiles, but I get a runtime error of:
InvalidCastException: Unable to cast object of type 'WebApp.Models.U1Profile' to type 'WebApp.ViewModels.U1MyProfile1ViewModel'.
Any ideas on how to do this easily?
Something simpler than assigning the model to the view-model field by field.
Set your View model like follow:
View modal
public class U1MyProfile1ViewModel
{
public List<SelectListItem> CountryList { get; set; }
public U1Profile U1Profile{get;set;}
public string othervariable{get;set;}
}
Controller
var myProfile = await _mainDbContext.U1Profiles
.AsNoTracking()
.FirstOrDefaultAsync(i => i.SiteUserId == mySiteUserId);
U1MyProfile1ViewModel myProfileViewModel = new U1MyProfile1ViewModel;
U1MyProfile1ViewModel.U1Profile=myProfile;
U1MyProfile1ViewModel.CountryList=yourcountrylist;
And finally just passed your viewmodal to View and you get your result.
For better understanding just see below link:
Link1
Link2

using a class method in MVC4

I just start do work on MVC4 Asp.Net
I have this class in my models
namespace PhoneBook.Models
{
public class User
{
public string Username { get; set; }
public string Password { get; set; }
public static String writeasd(){
return "asd";}
}
I have this method in my controller:
public ActionResult Main()
{
ViewBag.Username = Request.Form["username"];
ViewBag.Password = Request.Form["password"];
var user = new User()
return View(user);
}
However when I tried to call this method from my view like this:
#User.writeasd()
It gives error. What is the problem? Can you help me?
Note : I have #using PhoneBook.Models in the beginning of my view
When using a strongly typed view as you are there, you need two things.
One is a model directive
#model PhoneBook.Models.User
Then you can reference your model using the Model property of the view page.
So in your instance, you would use
#Model.writeasd()
HTH

How to render the checkbox in editor window with corresponding boolean value(i.e)checked or unchecked in MVC 4..?

Model:
public partial class TBLAppUser
{
public bool isActive { get; set; }
}
View:
#Html.CheckBoxFor(u => u.useredit.isActive)
you initialize an instance of TBLAppUser, set the IsActive of that instance to true, and pass that instance to the view. This is quite a simple situation. I think you better look at introductory tutorials found in here asp.net/mvc/overview/getting-started .
following is how your controller action would look like
[HttpGet]
public ActionResult Index()
{
var mainModel = //what ever the model you represent by u in the view
mainModel.useredit = new TBLAppUser{ isActive = true};
return View(mainModel);
}

RouteLink with Knockoutjs template item properties

I have a template rendering another template via foreach: viewModel.foos. On this template I would like to do something like this: #Html.RouteLink("View", "Foo", new { id = fooId, text = fooName }). Being fooId and fooName properties of the view model.
I've added this to my template:
<a data-bind="attr: { href: Url }">View</a>
And this to my Foo object:
public class Foo : FooBase {
public long FooId { get; set; }
public string FooName { get; set; }
public string Url {
get {
return string.Format("/foo/{0}/{1}", FooId, FooName
}
}
}
The cons:
Painfully scalable.
The pros:
Simplicity.
This does not answer question, but I will keep it here because it shows how you can do it for a more SPA like approuch.
If you are looking to move navigation to client check out Durandal or my light weight SPA engine
https://github.com/AndersMalmgren/Knockout.Bootstrap.Demo
Old answer before question was updated
You have to do this from your model (Which is a good thing because it should not be done in the view)
Let your subview model take the parameters as constructor arguments
MyViewModel = function() {
this.subView = ko.observable();
};
MyViewModel.prototype = {
loadSubView: function() {
this.subView(new SubViewModel(this.params));
}
};
edit: Tip test this lib for templating
http://jsfiddle.net/hwTup/