A lot of my plain content is in the database, accessed by a custom CMS. Around the application I display simple "thank you" messages, etc. which consist of a controller action (simplified):
public ActionResult DetailsUpdated()
{
return View();
}
and my view:
#Html.GetContent("DetailsUpdated")
I have quite a few of these and its quite annoying having a lot of view files with one-liners in. I want to be able to return that content as a View, I can do return ContentResult(ContentRepository.GetContent("KEY")); but this returns as plain-text and there is no master view rendered.
So, basically, grab the content from the DB via ContentRepository.GetContent("KEY") (returns a string) and inject it into a master view, where RenderBody() is called. I'd like to have a custom ActionResult so I can just do:
public ActionResult DetailsUpdated()
{
return DbContentResult();
}
and then the DbContentResult ActionResult will find the content key relative to the action and controller name, go to the database and retrieve the content and display it within its master view, no physical file view needed. Is this possible?
You may have one view file and refer to that view file from several actions:
public class FooBarController : Controller {
public ViewResult Foo() {
return View("FooView", ContentRepository.GetContent("KEY"));
}
}
In this case, you will be able to render the view whose path is ~/Views/Shared/FooView.cshtml (unless you override the default convention of course).
Edit:
As you indicated, you can make a custom ViewResult which does this for you:
public class DbContentResult : ViewResult {
public DbContentResult() {
this.ViewName = "FooView";
this.ViewData.Model = "Foo Model";
}
}
Usage:
public ActionResult Index() {
return new DbContentResult();
}
Or even better, write an extension method for Controller class which integrates with DbContentResult:
public static class ControllerExtensions {
public static ViewResult DbContentResult(this Controller controller) {
return new DbContentResult();
}
}
Usage:
public ActionResult Index() {
return this.DbContentResult();
}
for more detail about creating custom actionresult go here:-
http://www.professionals-helpdesk.com/2012/06/create-custom-actionresult-in-mvc-3.html
Related
I am trying to route url with and without parameter to two different methods but for some reason it always start first one.
Here is controller code:
public class ProductController : Controller
{
[Route("")]
[Route("Product")] //If i delete this one then it works how it is intended
public IActionResult Index()
{
//It always start this one
....
}
[Route("Product/{GroupID?}")]
public IActionResult Index(int GroupID)
{
....
}
}
I have a need, to make a multi-step process in which my, let's say "big model" will be updated every step and in the end saved to a database. For now, I made it this way:
[HttpGet]
public ActionResult StepOne()
{
return View();
}
[HttpPost]
public ActionResult StepOne(StepOneViewModel viewModel)
{
return RedirectToAction("StepTwo", "SameController", viewModel);
}
[HttpGet]
public ActionResult StepTwo(StepOneViewModel stepOneViewModel)
{
var stepTwoViewModel = new StepTwoViewModel()
{
stepOneViewModel = stepOneViewModel
};
return View(stepTwoViewModel );
}
[HttpPost]
public ActionResult StepTwo(StepTwoViewModel viewModel)
{
return RedirectToAction("StepThree", "SameController", viewModel);
}
... And so on. This approach seems to be good because it's strongly typed in a view, and I can have a separate validation for every "smaller" view model. On the other hand, I have to pass every parameter of the previous view model via Hidden inputs (making a partial for example, not to mess the code too much).
But is there another, better way to do so ? Is it possible to pass the whole object with many properties to the hidden or something similar ?
Using Entity Framework with a many-to-many relationship, I'm confused how to update that relationship from an ASP.NET MVC controller where the model is bound.
For example, a blog: where posts have many tags, and tags have many posts.
Posts controller edit action fails to lazy load tags entities:
public class PostsController : Controller
{
[Route("posts/edit/{id}")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "Id,Title,Tags")] Post post)
{
// Post tags is null
Post.tags.ToList();
}
}
Above, the HTTP Post binds properties to the model; however, the Post.tags relationship is null.
There's no way for me to query, .Include(p => p.Tags), or Attach() the post to retrieve the related tag entities using this [Bind()].
On the view side, I'm using a tokenizer and passing formdata - I'm not using a MVC list component. So, the issue is not binding the view formdata - the issue is that the .tags property is not lazy loading the entities.
This relationship is functional - from the Razor cshtml view I am able to traverse the collection and view children tags.
From the Post View, I can view tags (this works)
#foreach (var tag in Model.Tags) {
}
From the Tag View, I can view posts (this works)
#foreach (var post in Model.Posts) {
}
Likewise on create action from the Posts controller, I can create new tag entities and persist their relationship.
public class PostsController : Controller
{
[Route("posts/create")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "Title,Content")] Post post)
{
string[] tagNames = this.Request.Form["TagNames"].Split(',').Select(tag => tag.Trim()).ToArray();
post.Tags = new HashSet<Tag>();
Tag tag = new Tag();
post.Tags.Add(tag);
if (ModelState.IsValid)
{
db.posts.Add(post);
await db.SaveChangesAsync();
return RedirectToAction("Admin");
}
return View(post);
}
These relationships work everywhere except for this edit HTTP Post. For reference, they are defined as:
public class Post
{
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public virtual ICollection<Post> Posts { get; set; }
}
Only on HTTP Post of edit action can I not access tags of posts.
How can I load related tags to alter that collection?
Your problem has nothing to do with Entity framework. It is basically an issue when you post a model/viewmodel with a collection property from your form, the collection property becomes null.
You can solve this by using EditorTemplates.
It looks like you are using the entity classes generated by Entity framework in your views. Generally this is not a good idea because now your UI layer is tightly coupled to EF entities. What if tomorrow you want to change your data access code implemenation form EF to something else for any reasons ?
So Let's create some viewmodels to be used in the UI layer. Viewmodels are simple POCO classes. The viewmodels will have properties which the view absolutely need. Do not just copy all your entity class properties and paste in your viewmodels.
public class PostViewModel
{
public int Id { set; get; }
public string Title { set; get; }
public List<PostTagViewModel> Tags { set; get; }
}
public class PostTagViewModel
{
public int Id { set; get; }
public string TagName { set; get; }
public bool IsSelected { set; get; }
}
Now in your GET action, you will create an object of your PostViewModel class, Initialize the Tags collection and send to the view.
public ActionResult Create()
{
var v =new PostViewModel();
v.Tags = GetTags();
return View(v);
}
private List<PostTagViewModel> GetTags()
{
var db = new YourDbContext();
return db.Tags.Select(x=> new PostTagViewModel { Id=x.Id, TagName=x.Name})
.ToList();
}
Now, Let's create an editor template. Go to the ~/Views/YourControllerName directory and create a sub directory called EditorTemplates. Create a new view there with the name PostTagViewModel.cshtml.
Add the below code to the new file.
#model YourNamespaceHere.PostTagViewModel
<div>
#Model.TagName
#Html.CheckBoxFor(s=>s.IsSelected)
#Html.HiddenFor(s=>s.Id)
</div>
Now, in our main view (create.cshtml) which is strongly typed to PostViewModel, we will call Html.EditorFor helper method to use the editor template.
#model YourNamespaceHere.PostViewModel
#using (Html.BeginForm())
{
<label>Post title</label>
#Html.TextBoxFor(s=>s.Title)
<h3>Select tags</h3>
#Html.EditorFor(s=>s.Tags)
<input type="submit"/>
}
Now in your HttpPost action method, you can inspect the posted model for Tags collection.
[HttpPost]
public ActionResult Create(PostViewModel model)
{
if (ModelState.IsValid)
{
// Put a break point here and inspect model.
foreach (var tag in model.Tags)
{
if (tag.IsSelected)
{
// Tag was checked from UI.Save it
}
}
// to do : Save Post,Tags and then redirect.
}
model.Tags = GetTags(); //reload tags again
return View(model);
}
So since our HttpPost action's parameter is an object of PostViewModel, we need to map it to your Entity classes to save it using entity framework.
var post= new Post { Title = model.Title };
foreach(var t in model.Tags)
{
if(t.IsSelected)
{
var t = dbContext.Tags.FirstOrDefault(s=>s.Id==t.Id);
if(t!=null)
{
post.Tags.Add(t);
}
}
}
dbContext.Posts.Add(post);
await dbContext.SaveChangesAsync();
Your create method that missing an Id parametres of post that you want to insert tags.
You have to declare Post Id at the begining of the method.
[Route("posts/create")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "Title,Content")] Post post)
{
Post nPost= db.Posts.FirstOrDefault(x => x.Id == post.Id);
nPost.Id= post.Id;
nPost.Title= post.Title;
string[] tagNames = this.Request.Form["TagNames"].Split(',').Select(tag => tag.Trim()).ToArray();
foreach(string item in tagNames)
{
//First check if you got tag in Tags table
Tag tg= db.Tags.FirstOrDefault(x => x.Name.ToLower() == item.ToLower().Trim());
// If no row than create one.
if (tg== null)
{
tg= new Tag();
tg.Name= item;
db.Tags.Add(tg);
await db.SaveChanges();
}
// Now adding those tags to Post Tags.
if (nPost.Tags.FirstOrDefault(x => x.Id == tg.Id) == null)
{
nPost.Tags.Add(etk);
await db.SaveChanges();
}
}
if (ModelState.IsValid)
{
await db.SaveChangesAsync();
return RedirectToAction("Admin");
}
return View(post);
}
That's it.
This will be quite simple but
What is the best way of using classical webforms "response.write" in asp net MVC. Especially mvc5.
Let's say: I just would like to write a simple string to screen from controller.
Does response.write exist in mvc?
Thanks.
If the return type of your method is an ActionResult, You can use the Content method to return any type of content.
public ActionResult MyCustomString()
{
return Content("YourStringHere");
}
or simply
public String MyCustomString()
{
return "YourStringHere";
}
Content method allows you return other content type as well, Just pass the content type as second param.
return Content("<root>Item</root>","application/xml");
As #Shyju said you should use Content method, But there's another way by creating a custom action result, Your custom action-result could look like this::
public class MyActionResult : ActionResult
{
private readonly string _content;
public MyActionResult(string content)
{
_content = content;
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.Write(_content);
}
}
Then you can use it, this way:
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return new MyActionResult("content");
}
I have an experience in work with ASP.NET forms, but new to MVC.
How can I get data from shared views on postback?
In ASP.NET Forms I can write something like this:
ASP.NET Forms:
Model code:
public class MyModelItem
{
// Just TextBox is enough for editing this
public string SimpleProperty { get; set; }
// For this property separate NestedItemEditor.ascx is required
public MyModelNestedItem ComplexProperty { get; set; }
}
public class MyModelNestedItem
{
public string FirstProperty { get; set; }
public string SecondProperty { get; set; }
}
Behavior:
Control for editing MyModelNestedItem is separate ASCX control NestedItemEditor.ascx
This is just for example, MyModelNestedItem can be much more complex, I just want to give idea what I mean.
Now when I showing this item for editing, I'm showing one asp:TextBox and one NestedItemEditor.ascx. On page postback I'm gathering data from both and that's it.
Problem with MVC:
When I'm trying to implement this scenario with MVC, I'm using customized EditorFor (through using UIHint and creating shared view). So this shared view Views\Shared\EditorTemplates\MyModelNestedItem.cshtml can now display data that is already in MyModelNestedItem property but I have no idea how to make it return new entered data.
When parent controller recieves a post request, data seems to be in Request.Form, but which is civilized way to reach it? Sure, the best solution will be if data will fetch automatically into the MyModelItem.ComplexProperty.
The action which is called on post needs to be something like:
[HttpPost]
public ActionResult Index(MyViewModel mdl)
Then all the properties of the model which have input controls (or hidden inputs) on the form will have the data which was entered on the form (or passed to it or modified by javascript, in the case of hidden inputs).
This assumes that MyViewModel is the model referenced in your view.
Writing an ActionResult method in the controller with the complex type simply worked for me:
public class Topic
{
public Topic()
{
}
public DetailsClass Details
{
get;
set;
}
}
public class DetailsClass
{
public string TopicDetails
{
get;
set;
}
}
The view:
#modelTopic
#using (Html.BeginForm("Submit","Default"))
{
#Html.EditorFor(m=>m.Details)
#:<input type="submit" />
}
The controller:
public ActionResult Index()
{
Topic topic = new Topic();
return View( topic);
}
public ActionResult Submit(Topic t)
{
return View(t);
}
When submited, the Topic t contains the value i ented within the editor (Assuming You have a custom editor for the complex type, DetailsClass in my sample)