I'm not talking about the RenderSection and my question is not related direcly with layout page.
First of all i want to tell you what im trying to do. I have a
public class Module
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int Ordering { get; set; }
public string Position { get; set; }
public bool Published { get; set; }
public string ModulePath { get; set; }
public bool ShowTitle { get; set; }
}
table named Module. Modules are like Widgets. Please pay attention to the Position and ModulePath fields.
Positions are like ContentPlaceHolder defined in Layout page. In Mvc i define position like below.
RenderSection("PositionName", required:false)
ModulePath field holds the PartialView's location. They will be rendered in layout.
Problem starts here. How to do this programmatically.
Normally we use;
#section PositionName
{
#html.Partial(...)
}
to define a section in content page.
In Asp.net WebForms it is very easy to do.
ContentPlaceHolder.FindControl(PositionName).Controls.Add(..)
I need to specify section names programmatically, which is in a content page
Actually i need something like this;
foreach(var item in Model.Modules)
{
this.AddSection(item.Position, item.ModulePath, item.Ordering)
}
Manual way
#section PositionName
{
#html.Partial("~/Views/Shared/AModule.cshtml")
#html.Partial("~/Views/Shared/AnotherModule.cshtml")
#html.Partial("~/Views/Shared/AnotherModule.cshtml")
}
I tried;
#section #SectionName
{
...
}
ofcource failed.
So its very good idea to dynamically place PartialViews anywhere of page which their positions setted at administration panel. Thats what im trying to do.
From my knowledge there's no option to do this within ASP.NET MVC, but it should be do-able.
As pages are rendered into HTML they get added to the ViewContext object, so I imagine you'd want to replace areas of the HTML with your rendered view. You can access the current rendered HTML as a string like so:
var htmlContent = string.Empty;
var view = ViewEngines.Engines.FindView(ControllerContext, relativePath, null);
using (var writer = new StringWriter())
{
var context = new ViewContext(ControllerContext, view.View, ViewData, TempData, writer);
view.View.Render(context, writer);
writer.Flush();
htmlContent = writer.ToString();
}
With this in mind you could programatically replace areas of the string by using some kind of tag that you could match against.
You can define a section programmatically like this:
DefineSection("YoursectionName", async (a) =>
{
this.Write(await this.Component.InvokeAsync(widget.Name, widget.Parameters));
});
Essentially, you would do a Select over all the sections and define the section for each by invoking the given component and then writing the resulting HTML.
There's no way to dynamically add a section, but you can dynamically render things within an existing section. For example:
#section FooPosition
{
foreach (var module in models.Where(m => m.Position == "FooPosition"))
{
#Html.Partial(...);
}
}
In other words, you define sections for the available positions, and then allow the module to have only one of those available positions. Then, you just render whatever modules belong to that position inside the section.
Related
I am a newbie and creating a website where you can create your own custom quizes. Ive made a database that stores a class object mytests that consists of a name, and a list of questions parameter.
public class MyTests
{
public int ID { get; set; }
public string name { get; set; }
public string description { get; set; }
public List<MyQuestions> AllTestQuestions;
}
//using this object for questions
public class MyQuestions
{
public string QuestionDescription { get; set; }
public string MultipleChoiceCorrect { get; set; }
public string MultipleChoiceB { get; set; }
public string MultipleChoiceC { get; set; }
public string MultipleChoiceD { get; set; }
public string Answerexplanation { get; set; }
}
I'm using the default database code generated by visual studio. I have no problem adding this test object(mytest) to the database, but what I want to do is that on the edit.cshtml view I want to be able to add elements to the question list before returning the object to the database saved.
The problem is I don't know how to edit the model object from the view, or if this is even possible. I could maybe get it to work through a redirect? but I thought that adding the elements directly from the view would be easier. Is it possible to modify the model.object inside a view from the view (putting security concerns aside)?
For example model.title = something;
or
model.list.add()
Is anything like this possible?
If this question is not clear please let me know and I will try to clarify in the comments.
Yes, it is possible to edit the model from within the view.
From within your .cshtml file specify the view model using the #model declaration, then edit the model like so:
#model Namespace.For.MyTests
#Model.name = "Hello World";
<p>#Model.name</p>
Whilst this would work, it's not really what the view is for so I wouldn't recommend it.
The view is about presenting your data, not mutating it - that should be done in the controller, or domain layer. As soon as the user leaves the page then your changes will be lost due to the stateless nature of the web (.NET MVC passes data to the view from the controller, then ends the request).
This should be done at the controller level. You could do it on a view but it's not what the view is for.
Your issue is that if the page is refreshed you will lose you content, so if you do anticipate on the page refreshing you will need a way in which to temporarily hold the information before it being saved.
On a side note, I'd also consider renaming your classes "MyTests" to "MyTest" (singular) and "MyQuestions" to "MyQuestion"... it's just good practice because then you'd have a List of singleton "MyQuestion" in a "MyTest". EntityFramework Codefirst will pluralise the names when the database is created/update.
Well im kinda new in Asp.net Mvc and im learning alone from scratch, i have a aplicattion that controls expends and earnings and what i am trying to do now is, basing on a list of earnings and expends give me the balance from a user, im having a lot of problems trying to control this and i dont know if i am doing it the right way
Here is my model:
public class Balance
{
public int BalanceId { get; set; }
public List<Expense> Despesas { get; set; }
public List<Earning> Rendimentos { get; set; }
public string ApplicationUserId { get; set; }
}
Soo what i did was, first trying to control when the user inserts a Earning or a row like, verifying if the User already exists on the database in the control method Create on the expenses and in the earning, if it doesnt exist he add the aplicationUserId and the expensive or the earning.
I want that the balance appears in every page, soo i added this to my Layout.cshtml
<li>#Html.Action("GetBalance", "Home")</li>
it calls the controller GetBalance:
public PartialViewResult GetBalance()
{
var userId = User.Identity.GetUserId();
var balance = db.Balance.Where(d => d.ApplicationUserId == userId);
return PartialView("_GetBalance",balance);
}
Send to the view _GetBalance the balance model:
#model <MSDiary.Models.Balance>
<p>Saldo: #GetBalance()</p>
#functions
{
HtmlString GetBalance()
{
decimal saldo = 0;
if (Model.Expense.Count != 0 || Model.Earning.Count != 0)
{
foreach (var item in Model.Despesas)
{
balance += item.EarningValue;
}
foreach (var item in Model.Rendimentos)
{
balance -= item.ExpenseValor;
}
}
return new HtmlString(balance.ToString());
}
}
What i want to know is, if there is a easyer way to do this, or what i can do to do what i want, i cant get it why my view expects something different can someone explain me what i am doing wrong?
Ps: Sorry for the long post and English, but i want to learn more :)
Firstly, the model #model <MSDiary.Models.Balance> needs to be changed to:
#model IEnumerable<MSDiary.Models.Balance>
Also, the method GetBalance should ideally be placed in a class not in GetBalance partial view. You could achieve this two ways, either through extension methods or have a Balance View Model that has the calculated balance as a property which is then passed down to your view.
As an example via an extension method:
public static class BalanceExtensions
{
public static string GetBalance(this Balance balance)
{
string displayBalance = "0:00";
// Your logic here
return displayBalance;
}
}
And then in your Partial View you can use the new HTML Helper:
#Html.GetBalance();
As an additional note I would change List to IEnumerable for expenses and earnings as it appears you are only exposing the data and not manipulating the data.
Your model would then look like:
public class Balance
{
public int BalanceId { get; set; }
public IEnumerable<Expense> Despesas { get; set; }
public IEnumerable<Earning> Rendimentos { get; set; }
public string ApplicationUserId { get; set; }
}
#Filipe Costa A few things here.
You should probably name your view the same thing as your method. The underscore preceding the name is fairly standard so I would suggest using that same name for the method. If the name of the method and view are the same you can simply pass in the model and not have to do the name + model signature of PartialView method. It's simpler.
Aside from that your code is fine but your .cshtml partial view should have this for the first line. That will accept the list you're passing.
#model IEnumerable<MSDiary.Models.Balance>
<h1>#Model.BalanceId</h1>
#*Do other stuff!*#
I have something like this
public class ResumeVm
{
public ResumeVm()
{
EducationVms = new List<EducationVm>();
}
public Guid UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<EducationVm> EducationVms { get; set; }
}
public class WorkExperienceVm
{
public string Title { get; set; }
}
I now want to make an editor template for each of the EducationVms, I made a Template for 1 Education Vm and tried to use
#Html.EditorForModel("WorkExperienceVm")
but it does not know how to pass in the EducationVms
If I do
#Html.EditorForModel("WorkExperienceVm", #Model.EducationVms )
It gets made as it expects only 1 Vm to be sent in.
// View (WorkExperienceVm)
#model ViewModels.WorkExperienceVm
#Model.Title
The EditorForModel overload that you're using is incorrect. EditorForModel produces an editor template for the current model (i.e. the ResumeVm) and the string you're passing in is the additional view data object, not the name of the view.
I'm assuming that "WorkExperienceVm" is the name of the view. Try using EditorFor:
#for(int i = 0; i < Model.EducationVms.Count; i++)
{
Html.EditorFor(m => m.EducationVms[i], "WorkExperienceVm")
}
An alternative is to create a template that's actually called EducationVm.cshtml and type it to EducationVm, then you can just do the following and the framework will figure out that you want the template called for each item in the collection:
#Html.EditorFor(m => m.EducationVms)
Unfortunately this approach can't be achieved using UIHints or passing in the view name manually into the helper, though that's fairly unlikely to get in your way if you don't mind adhering to strict naming conventions for your templates.
I wrote another answer a while ago explaining the differences between the different helpers for editor templates. It deals specifically with the "label" helpers but the same principles apply to the "editor" helpers.
I have a model with BlogPost and Tag entities.
public class Tag
{
public string Name { get; set; }
public string Slug { get; set; }
}
public class BlogPost : ITaggable
{
public BlogPost()
{
this.Tags = new List<Tag>();
}
public ICollection<Tag> Tags { get; set; }
[AllowHtml]
public string Text { get; set; }
}
On admin BlogPost edit page tags should be represented in one text input, separated by comma. Pretty much like StackOverflow tag's input.
It means serialization of ICollection to a string, and deseralization back to a collection.
I'm happy with default model binder, beside the Tags collection of BlogPost.
There are few options I know to handle it:
Custom Model Binder - with a need to override all BlogPost bindings - there are really a lot properties in a real BlogPost entity.
Create a text input not related to a BlogPost model, and get/set it's values using Form parameters. It requires more manual work too.
Something else....
How would you implement tags input for a blog post?
UPDATE:
For now I try this:
public List<Tag> Tags { get; set; }
public string TagsString {
get
{
var tags = Tags.Select(tg => tg.Name).ToArray();
var res = string.Join(",", tags);
return res;
}
set
{
var tags = value.Split(',');
Tags = new List<Tag>();
foreach (var tag in tags)
Tags.Add(new Tag { Name = tag });
}
}
This is the perfect case to introduce a ViewModel between your view and your model.
A ViewModel will allow you to modify the representation of your data (split them in a string) for a specific view (it's a best practice to create one per view) without altering the integrity of your model.
Then, your controller will be in charge of the serialization/deserialization.
Here are a few articles talking about the need and the how of ViewModels in ASP.NET MVC applications:
ViewModel best practices
ViewModel patterns
View Model pattern and AutoMapper
ASP.NET MVC Tip #50 – Create View Models
I would maintain the UI/UX to show them comma separated in a single text box.
As far as getting them as a collection in the controller, I personally would stick with the default modelbinder, but that doesn't necessarily mean it's the correct way.
I would do something akin to your #2, however I would use a <select multiple="multiple"> so that there was 1 <option selected="selected">TagName/Slug</option> element for each tag.
As for maintaining the dependency / binding between the text box and the multi-select list, I would use knockoutjs for this. Parse the commas in the text box, and update the (hidden) multi-select. When submitted to the controller, ignore the text box (or just don't make it part of your server-side viewmodel).
Let's say i have 2 files located in the same folder.
/Test/View.cshtml
<h1>File that needs to be loaded in to a string</h1>
/Test/Content.cs
public class Content {
public string GetView()
{
Return View("/Test/View.cshtml",someModel)
}
}
It should not cair about the RouteData from Web.Config
The point of doing this is, so that i am able to retrieve the GetView and use it elsewhere.
I know this question and approch is wierd but i am in a uniq situation developeing a CMS system, so i really need something like this.
How could i achieve this :)?
Update: Explanation
_Layout.cshtml
This file has no RenderBody as it normally has. Instead it has different Areas like this one.
#{
Render r = new Render("Content");
}
#r.Print()
Each area are printing out different modules, fx: a newsletter or a gallery. And for that to be possible this is done:
public interface IModule
{
string Name { get; set; }
int Id { get; set; }
string View();
}
public class ModuleList
{
public List<IModule> Modules = new List<IModule>();
public ModuleList()
{
Modules.Add(new ContentView() { Name = "Content" });
Modules.Add(new GalleryView() { Name = "Gallery" });
Modules.Add(new NewsletterView() { Name = "Newsletter" });
}
}
And here is is the ContentView Class (One of many Modules)
public class ContentView: IModule
{
public string Name { get; set; }
public int Id { get; set; }
DbModulesDataContext db = new DbModulesDataContext();
public string View()
{
var q = (from c in db.mContents
where c.Id == Id
select c).FirstOrDefault();
return ("<h1>"+q.Html+"</h1>");
}
}
As you can see right now the html is inline with the c# but i want it the other way around. (i want the View() to work together with a cshtml file)
Does it make a little more sence now?
I finaly found my solution!
It's right there!
http://razorengine.codeplex.com/
Thank you for trying though :)