Is it possible to use two different controllers for the same url?
This is needed because I need the URL to always remain the same, but it should use different controllers. My controllers (Apples, Bananas, etc.) and views are separated into each own project.
I need a action in my main MVC project to return a action/view from either the Bananas or Apples project depending on some logic.
So how would I go ahead to always have the same url but return actions/views from different controllers?
I'm using MVC 4
Your URLs should be where the logic for selecting your controller is. Maybe you need to reorganise your project to have a single controller and put the other logic in the controller action for filling the model?
However, if you insist on going this route you will likely need to override CreateController in the DefaultControllerFactory, this is the class that instantiates your controller, usually based on your controller name. Here is an example in one of my projects:
public class ErrorHandlingControllerFactory : DefaultControllerFactory
{
/// <summary>
/// Injects a custom attribute
/// on every action that is invoked by the controller
/// </summary>
/// <param name="requestContext">The request context</param>
/// <param name="controllerName">The name of the controller</param>
/// <returns>An instance of a controller</returns>
public override IController CreateController(
RequestContext requestContext,
string controllerName)
{
var controller =
base.CreateController(requestContext,
controllerName);
var c = controller as Controller;
if (c != null)
{
c.ActionInvoker =
new ErrorHandlingActionInvoker(
new HandleErrorWithELMAHAttribute());
}
return controller;
}
}
You will need to set your route up to pass a known controller name (horrible magic strings...), test for this controller name, and if detected run your logic to get the actual controller name and pass this in to base.CreateController.
I wrote these codes. I hope that it helps you. I used hidden field to understand which method will run.
these are my models:
namespace MvcSameController.Models
{
public class RouteModel
{
public SampleModel1 SampleModel1 { get; set; }
public SampleModel2 SampleModel2 { get; set; }
}
public class SampleModel1
{
public int Id { get; set; }
public string Name { get; set; }
}
public class SampleModel2
{
public int Id { get; set; }
public string Surname { get; set; }
}
}
this is controller:
using System.Web.Mvc;
using MvcSameController.Models;
namespace MvcSameController.Controllers
{
public class SameController : Controller
{
//
// GET: /Same/
public ActionResult Index()
{
return View();
}
[HttpPost]
public void Index(RouteModel routeModel, string type)
{
if (type == "1")
{
//Code for type 1
}
else if (type == "2")
{
//Code for type 2
}
}
}
}
and view :
#{
ViewBag.Title = "Index";
}
#model MvcSameController.Models.RouteModel
<section id="loginForm">
<h2>Type1 </h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.Hidden("type",1)
<fieldset>
<legend>Type1 Form</legend>
<ol>
<li>
#Html.LabelFor(m => m.SampleModel1.Name)
#Html.TextBoxFor(m => m.SampleModel1.Name)
#Html.ValidationMessageFor(m => m.SampleModel1.Name)
</li>
</ol>
<input type="submit" value="Run Method1" />
</fieldset>
}
</section>
<section id="loginForm">
<h2>Type2</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.Hidden("type",2)
<fieldset>
<legend>Type2 Form</legend>
<ol>
<li>
#Html.LabelFor(m => m.SampleModel2.Surname)
#Html.TextBoxFor(m => m.SampleModel2.Surname)
#Html.ValidationMessageFor(m => m.SampleModel2.Surname)
</li>
</ol>
<input type="submit" value="Run Method2" />
</fieldset>
}
</section>
you can download my sample from here
Related
What I was doing with ASP.NET MVC 5
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MaxLengthAttribute), typeof(MyMaxLengthAttributeAdapter));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredAttribute), typeof(MyRequiredAttributeAdapter));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MinLengthAttribute), typeof(MyMinLengthAttribute));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(EmailAddressAttribute), typeof(MyEmailAddressAttributeAdapter));
Now I'm migrating it to ASP.NET core 6
We can't use DataAnnotationsModelValidatorProvider anymore so I'm trying to use IValidationAttributeAdapterProvider, which doesn't work properly for me.
My codes
My IValidationAttributeAdapterProvider is below.
public class MyValidationAttributeAdapterProvider : ValidationAttributeAdapterProvider, IValidationAttributeAdapterProvider
{
IAttributeAdapter? IValidationAttributeAdapterProvider.GetAttributeAdapter(
ValidationAttribute attribute,
IStringLocalizer? stringLocalizer)
{
return attribute switch
{
EmailAddressAttribute => new MyEmailAddressAttributeAdapter((EmailAddressAttribute)attribute, stringLocalizer),
MaxLengthAttribute => new MyMaxLengthAttributeAdapter((MaxLengthAttribute)attribute, stringLocalizer),
MinLengthAttribute => new MyMinLengthAttribute((MinLengthAttribute)attribute, stringLocalizer),
RequiredAttribute => new MyRequiredAttributeAdapter((RequiredAttribute)attribute, stringLocalizer),
_ => base.GetAttributeAdapter(attribute, stringLocalizer),
};
}
}
My model class is below.
public class LogInRequestDTO
{
[Required]
[EmailAddress]
[MaxLength(FieldLengths.Max.User.Mail)]
[Display(Name = "mail")]
public string? Mail { get; set; }
[Required]
[MinLengthAttribute(FieldLengths.Min.User.Password)]
[DataType(DataType.Password)]
[Display(Name = "password")]
public string? Password { get; set; }
}
And in my Program.cs, I do like below.
builder.Services.AddControllersWithViews()
.AddDataAnnotationsLocalization(options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(Resources));
});
builder.Services.AddSingleton<IValidationAttributeAdapterProvider, MyValidationAttributeAdapterProvider>();
What happed to me
I expect GetAttributeAdapter is called for each attribute like EmailAddressAttribute, MaxLengthAttribute, etc.
But it's called only once with EmailAddressAttribute.
So, all other validation results are not customized by my adaptors.
If I remove [EmailAddress] from the model class, GetAttributeAdapter is never called.
Am I missing something?
Added on 2022/05/24
What I want to do
I want to customize all the validation error message.
I don't want to customize for one by one at the place I use [EmailAddress] for example.
I need the server side validation only. I don't need the client side validation.
Reproducible project
I created the minimum sample project which can reproduce the problem.
https://github.com/KuniyoshiKamimura/IValidationAttributeAdapterProviderSample
Open the solution with Visual Studio 2022(17.2.1).
Set the breakpoint on MyValidationAttributeAdapterProvider.
Run the project.
Input something to the textbox on the browser and submit it.
The breakpoint hits only once with EmailAddressAttribute attribute.
The browser shows the customized message for email and default message for all other validations.
Below is a work demo, you can refer to it.
In all AttributeAdapter, change your code like below.
public class MyEmailAddressAttributeAdapter : AttributeAdapterBase<EmailAddressAttribute>
{
// This is called as expected.
public MyEmailAddressAttributeAdapter(EmailAddressAttribute attribute, IStringLocalizer? stringLocalizer)
: base(attribute, stringLocalizer)
{
//attribute.ErrorMessageResourceType = typeof(Resources);
//attribute.ErrorMessageResourceName = "ValidationMessageForEmailAddress";
//attribute.ErrorMessage = null;
}
public override void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-must-be-true", GetErrorMessage(context));
}
// This is called as expected.
// And I can see the message "Input the valid mail address.".
public override string GetErrorMessage(ModelValidationContextBase validationContext)
{
return GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName());
}
}
In homecontroller:
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult Index([FromForm][Bind("Test")] SampleDTO dto)
{
return View();
}
Index view:
#model IV2.Models.SampleDTO
#{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<h4>SampleDTO</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Index">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Test" class="control-label"></label>
<input asp-for="Test" class="form-control" />
<span asp-validation-for="Test" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Result1:
Result2:
I found the solution.
What I have to use is not ValidationAttributeAdapterProvider but IValidationMetadataProvider.
This article describes the usage in detail.
Note that some attributes including EmailAddressAttribute have to be treated in special way as describe here because they have default non-null ErrorMessage.
I confirmed for EmailAddressAttribute and some other attributes.
Also, there's the related article here.
I'm trying to Insert my product properties into SQL table, I have a razor page that gets input data that needs to be inserted but i don't know how to insert them
This is my product model:
namespace Market.ViewModels
{
public class ProductListView
{
public Guid ID { get; set; }
public string ProductName { get; set; }
public decimal ProductPrice { get; set; }
}
}
This is my razor page :
I made a simple example, using EF Core code first to create a database and then query the data, the process of binding the value to the page is as follows.
1.The first dependency packages used are as follows:
Model:
public class ProductListView
{
[Key]
public Guid ID { get; set; }
public string ProductName { get; set; }
public double ProductPrice { get; set; }
}
I modified your ProductPrice type, because there will be problems with this type during migration. If you must change the type, refer to this article:
http://jameschambers.com/2019/06/No-Type-Was-Specified-for-the-Decimal-Column/
Create Model and Context Classes:
Now you can add the database context
: name the class TestDbContext and click Add and change the code in TestDbContext.cs as follows:
public class TestDbContext:DbContext
{
public TestDbContext(DbContextOptions<TestDbContext> options) : base(options)
{
}
public DbSet<ProductListView> productListViews { get; set; }
}
Connection string you need to write inside the appsetting.json file as follows:
"ConnectionStrings": {
"DefaultDatabase": "Your DB"
}
In ASP.NET Core, services such as the DB context must be registered with the dependency injection container. The container provides the service to controllers.
Update Startup.cs with the following highlighted code:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<TestDbContext>(item => item.UseSqlServer(Configuration.GetConnectionString("DefaultDatabase")));
}
In order to create the migration code, we use the "add-migration MigrationName" command. After the add migration command is successfully executed, it will create a folder named "Migration" in the project. We only created the migration responsible for creating the database and its tables. script. But we have not yet created the actual database and tables. So let's execute the migration script and generate the database and tables. Therefore, to execute the migration script we must execute the'update-database' command.
Next, let us create a context class, define the database connection and register the context. Then perform the query work in the controller, and then return the data.
public class TestController : Controller
{
private readonly TestDbContext _context;
public TestController(TestDbContext context)
{
_context = context;
}
public IActionResult Test(ProductListView product)
{
var value = _context.productListViews.SingleOrDefault(item => item.ProductPrice == 12.1);
product.ProductName = value.ProductName;
product.ProductPrice = value.ProductPrice;
return View(product);
}
}
View:
#model WebApplication132.Models.ProductListView
<h1>AddProducts</h1>
<div class="row">
<div class="col-md-12">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="ProductName"></label>
<input asp-for="ProductName" class="form-control" />
</div>
<div class="form-group">
<label asp-for="ProductPrice"></label>
<input asp-for="ProductPrice" class="form-control" />
</div>
<button type="Add Product" class="btn-primary">Add Product</button>
</form>
</div>
</div>
Db data:
Result:
I am new to MVC.Basically I need to pass values entered in the textbox from my view to controller action method. As I enter the values in the text box and click the enter button I need to display the value on the screen. I am currently unable to do so. Please find my code below
The model class
public class ProteinTrackingService
{
public int? Total { get; set; }
public int Goal { get; set; }
public void AddProtein(int? amount)
{
Total += amount;
}
}
The controller class
public class ProteinTrackerController : Controller
{
ProteinTrackingService proteinTrackingService = new ProteinTrackingService();
// GET: ProteinTracker
public ActionResult Index()
{
ViewBag.Total = proteinTrackingService.Total;
ViewBag.Goal = proteinTrackingService.Goal;
return View();
}
// GET: ProteinTracker/Details/5
public ActionResult AddProtein(ProteinTrackingService model)
{
proteinTrackingService.AddProtein(model.Total);
ViewBag.Total = proteinTrackingService.Total;
ViewBag.Goal = proteinTrackingService.Goal;
return View("Index");
}
}
The view
using (Html.BeginForm("ProteinTracker", "AddProtein",FormMethod.Post))
{
#Html.AntiForgeryToken()
<form>
<div class="form-horizontal">
<h4>Protein Tracker</h4>
<hr />
Total : #ViewBag.Total
Goal : #ViewBag.Goal
<input id="Text1" type="text" value="TextInput" /> <input type="Submit" value="Add" />
</div>
</form>
}
I am modifying the code above based on your suggestions. I basically need to display the following in the view
Total : value
Goal : value
Textbox control (To enter the total) Button (pass the total to contoller) Please note that when the user clicks the Add button the total should show in above field Total : value.
New View
#using (Html.BeginForm( "AddProtein","ProteinTracker", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Protein Tracker</h4>
<hr />
#Html.LabelFor(m => m.Total, "Total" ) <hr />
#Html.LabelFor(m => m.Goal, "Goal") <hr />
#Html.TextBoxFor(m => m.Total) <hr />
<input type="Submit" value="Add" />
</div>
}
New Controller
public class ProteinTrackerController : Controller
{
ProteinTrackingService proteinTrackingService = new ProteinTrackingService();
// GET: ProteinTracker
public ActionResult Index()
{
var model = new ProteinTrackingService()
{ Total = proteinTrackingService.Total, Goal = proteinTrackingService.Goal };
return View(model);
}
// GET: ProteinTracker/Details/5
public ActionResult AddProtein(ProteinTrackingService model)
{
proteinTrackingService.AddProtein(model.Total);
model.Total = proteinTrackingService.Total;
model.Goal = proteinTrackingService.Goal;
return View("Index",model);
}
}
You need to add the HttpPost attribute to your action.Looking at your form #using (Html.BeginForm( "AddProtein","ProteinTracker", FormMethod.Post)) , apparently you are sending a post request to your controller.
[HttpPost]
public ActionResult AddProtein(ProteinTrackingService model)
{
proteinTrackingService.AddProtein(model.Total);
model.Total = proteinTrackingService.Total;
model.Goal = proteinTrackingService.Goal;
return View("Index",model);
}
First of all your this syntax
using (Html.BeginForm("ProteinTracker", "AddProtein", FormMethod.Post))
already creates a form tag when html generates. No need to create from tag again in it.
So for your want, in view you need give to your input field a name
<input id="Text1" type="text" value="TextInput" name="textname"/>
and add this name as parameter in your controller method like that
public ActionResult AddProtein(ProteinTrackingService model,string textname)
{
// your code
return View("Index");
}
It will pass your textfield value from view to controller. For clearing your concept you may visit Loopcoder.com
I need to use hdNoOfColumns and hdNoOfRows in a controller page which are defined as hidden type in View page. I am getting a "context does not exist" error. How do refer to a hidden type ID in the controller page?
View:
<input id="hdNoOfRows" type="hidden" name="hdNoOfRows" />
<input id="hdNoOfColumns" type="hidden" name="hdNoOfColumns"/>
Controller:
hdNoOfColumns.Value = count.ToString();
dsCount = ds.Tables[0].Rows.Count;
hdNoOfRows.Value = dsCount.ToString();
seatCount = dsS.Tables[0].Rows.Count;
asp.net mvc does not work as asp.net web forms, you need to create a model and then use it:
First Create a Model:
public class TableModel
{
public string NoOfColumns { get; set;}
public string NoOfRows { get;set; }
}
Now in your controller action:
public class BarController : Controller
{
public ActionResult Foo()
{
TableModel model = new TableModel();
model.NoOfColumns= count.ToString();
dsCount = ds.Tables[0].Rows.Count;
model.NoOfRows= dsCount.ToString();
seatCount = dsS.Tables[0].Rows.Count;
return View(model);
}
}
Now in your View use HiddenFor() :
#model TableModel
#Html.HiddenFor(x=>x.NoOfColumns)
#Html.HiddenFor(x=>x.NoOfRows)
post them by putting them inside form :
and For posting back values to controller you will have to put them in a form:
#model TableModel
#using(Html.BeginForm("Foo", "Bar", FormMethod.Post))
{
<input id="hdNoOfRows" type="hidden" name="hdNoOfRows" />
<input id="hdNoOfColumns" type="hidden" name="hdNoOfColumns"/>
<input type="submit" value="Post"/>
}
and then in your controller controller handle in post action of it:
public class BarController : Controller
{
public ActionResult Foo()
{
TableModel model = new TableModel();
model.NoOfColumns= count.ToString();
dsCount = ds.Tables[0].Rows.Count;
model.NoOfRows= dsCount.ToString();
seatCount = dsS.Tables[0].Rows.Count;
return View(model);
}
[HttpPost]
public ActionResult Foo(TableModel model)
{
// do saving in db or whatever business logic
return View(model);
}
}
I was trying to implement Listview control of Kendo UI for MVC. I am trying to bind the list view with my model but I am getting this error :
"CS1977: Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type"
I have checked some other questions on stackoverflow with the same error but I am unable to know the cause for this error as this is kendo Syntax and there is nothing wrong with my code as far as I know.
The error is in this line::.DataSource(ds => ds
View Page:
#{
ViewBag.Title = "Courses";
}
#using Kendo.Mvc.UI
<h2>Courses</h2>
Back
<div class="bodywrap">
<div class="CommonClass">
#( Html.Kendo().ListView<K_SampleProject.Models.CourseModel>(Model)
.Name("listView")
.TagName("div")
.ClientTemplateId("template")
.DataSource(ds => ds
.Model(model =>
{
//The unique identifier (primary key) of the model is the ProductID property
model.Id(p => p.ProductID);
// Declare a model field and optionally specify its default value (used when a new model instance is created)
model.Field(p => p.ProductName).DefaultValue("N/A");
// Declare a model field and make it readonly
model.Field(p => p.UnitPrice).Editable(false);
})
)
.Pageable()
)
</div>
</div>
<script type="text/x-kendo-tmpl" id="template">
<div class="product">
<img src="#Url.Content("~/content/web/foods/")${ProductID}.jpg" alt="${ProductName} image" />
<h3>${ProductName}</h3>
<dl>
<dt>Price:</dt>
<dd>${kendo.toString(UnitPrice, "c")}</dd>
</dl>
</div>
</script>
Model
namespace K_SampleProject.Models
{
public class CourseModel
{
public List<tbl_Courses> CourseList { get; set; }
public string ProductID { get; set; }
public string ProductName { get; set; }
public string UnitPrice { get; set; }
}
}
Controller
public ActionResult Courses()
{
CourseModel Model = new CourseModel();
RegistrationService ObjService = new RegistrationService();
Model.CourseList = ObjService.GetCourses();
return View(Model);
}
The main error in your code is that you passing single CourseModel class to the list, when it expects the List of CourseModel.
So, your Controller should looks like:
public ActionResult Courses()
{
List<CourseModel> result;
CourseModel Model = new CourseModel();
RegistrationService ObjService = new RegistrationService();
Model.CourseList = ObjService.GetCourses();
result.Add(Model);
return View(result);
}
I also advise:
Add #model List<CourseModel> in top of the View
If it is a PartialView (not main view like index) change return for: return PartialView(result);